OpenWorldVGDL/javascript/core/ontology/vgdl-sprite.js
2025-01-17 21:42:05 +08:00

773 lines
19 KiB
JavaScript

import {
clone,
defaultDict,
new_id,
random,
triPoints,
unitVector,
} from "../tools.js";
import { ContinuousPhysics, GridPhysics } from "./physics.js";
import {
BASEDIRS,
BLACK,
BLUE,
GRAY,
ORANGE,
PURPLE,
RED,
RIGHT,
} from "./constants.js";
import { killSprite } from "./effect";
export class VGDLSprite {
transformedBy = {};
name = null;
COLOR_DISC = [20, 80, 140, 200];
is_static = false;
only_active = false;
is_avatar = false;
is_stochastic = false;
cooldown = 0;
mass = 1;
physicstype = null;
shrinkfactor = 0;
dirtyrects = [];
size = [1, 1];
lastrect = null;
physicstype = GridPhysics;
speed = 0;
ID = 0;
direction = null;
color = "#ffffff";
orientation = [0, 0];
location = { x: 0, y: 0 };
healthPoints = -1;
limitHealthPoints = 1000;
maxHealthPoints = -1;
hidden = false;
blueprint = undefined;
/**
* @type {Array<string>}
*/
stypes = [];
constructor(pos, size, args = {}) {
args = args ?? {};
this.name = args.key || null;
this.location = pos ? { x: pos[0], y: pos[1] } : this.location;
// this.visual_location = {...this.location};
this.size = size ?? this.size;
this.lastlocation = { x: this.location.x, y: this.location.y };
this.physicstype = args.physicstype || this.physicstype || GridPhysics;
this.physics = new this.physicstype();
this.physics.gridsize = this.size;
this.speed = args.speed || this.speed;
this.cooldown = args.cooldown || this.cooldown;
this.ID = new_id();
this.direction = null;
this.color = args.color || this.color;
this.image = args.image;
this.healthPoints = args.healthPoints || this.healthPoints;
this.limitHealthPoints = args.limitHealthPoints || this.limitHealthPoints;
this.maxHealthPoints = args.maxHealthPoints || this.healthPoints;
this.hidden = args.hidden || this.hidden;
this.blueprint = args.blueprint || this.blueprint;
// iterate over kwargs
// this.extend(args);
if (args) {
Object.keys(args).forEach((name) => {
const value = args[name];
try {
this[name] = value;
} catch (e) {
console.error(`error: ${e}`);
}
});
}
// how many timesteps ago was the last move
this.lastmove = 0;
// management of resources contained in the sprite
this.resources = new defaultDict(0);
// this.visual_location = {...this.location}
// this.next_location = {...this.location}
// this.current_location = {...this.location}
}
get visual_location(){
return this.location;
}
// get location() {
// // console.log("get visual location")
// return this.next_location;
// }
// set location(value) {
// // if(this.name === "avatar")
// // console.log("set location")
// // console.log(JSON.stringify(this.current_location), JSON.stringify(this.next_location), JSON.stringify(value));
// if(this.next_location){
// this.visual_location = {...this.next_location};
// this.current_location = {...this.next_location};
// }else{
// this.visual_location = {...value};
// this.current_location = {...value};
// }
// this.next_location = {...value};
// }
update(game, delta) {
// this.lastmove += delta;
this.lastmove ++;
// if(this.name === "avatar")
// console.log(JSON.stringify(this.current_location), JSON.stringify(this.visual_location), JSON.stringify(this.next_location))
// this.current_location = {...this.next_location};
// this.visual_location = {...this.location};
if (!this.is_static && !this.only_active) {
this.physics.passiveMovement(this, delta);
}
// this.visual_location = {...this.location};
}
subUpdate(game, sub_idx, sum_idx) {
// if(this.name === "avatar")
// console.log(JSON.stringify(this.current_location), JSON.stringify(this.next_location))
// this.visual_location = {
// x: (1-sub_idx/sum_idx) * this.current_location.x + (sub_idx/sum_idx) * this.next_location.x,
// y: (1-sub_idx/sum_idx) * this.current_location.y + (sub_idx/sum_idx) * this.next_location.y
// }
// this.visual_location = {...this.next_location}
}
_updatePos = (orientation, speed = null) => {
if (speed === null) speed = this.speed;
if (
this.cooldown <= this.lastmove &&
Math.abs(orientation[0]) + Math.abs(orientation[1]) !== 0
) {
this.lastlocation = { x: this.location.x, y: this.location.y };
this.location = {
x: this.location.x + orientation[0] * speed,
y: this.location.y + orientation[1] * speed,
};
this.lastmove = 0;
}
};
_velocity() {
if (this.speed === null || this.speed === 0 || !("orientation" in this))
return [0, 0];
else
return [
this.orientation[0] * this.speed,
this.orientation[1] * this.speed,
];
}
lastdirection() {
return [
this.location.x - this.lastlocation.x,
this.location.y - this.lastlocation.y,
];
}
_draw(game) {}
_drawResources = (game, screen, location) => {};
_clear = (screen, background, double = null) => {};
toString() {
return `${this.name} at (${this.location.x}, ${this.location.y})`;
}
}
export class EOS extends VGDLSprite {
constructor(pos, size, args) {
super(pos, size, args);
this.ID = -1;
}
}
export class Immovable extends VGDLSprite {
constructor(pos, size, args) {
// args.color = args.color || GRAY;
super(pos, size, args);
this.is_static = args.is_static || true;
}
}
export class Passive extends VGDLSprite {
constructor(pos, size, args) {
// args.color = args.color || RED;
super(pos, size, args);
}
}
export class Flicker extends VGDLSprite {
constructor(pos, size, args) {
// args.color = args.color || RED;
super(pos, size, args);
this._age = 0;
this.limit = args.limit || 1;
}
update(game, delta) {
super.update(game, delta);
if (this._age > this.limit) killSprite(this, null, game);
this._age++;
}
}
export class SpriteProducer extends VGDLSprite {
constructor(pos, size, args) {
super(pos, size, args);
}
}
export class Portal extends SpriteProducer {
constructor(pos, size, args) {
super(pos, size, args);
this.is_static = true;
// this.color = BLUE;
}
}
export class SpawnPoint extends SpriteProducer {
constructor(pos, size, args) {
// args.color = args.color || BLACK
args.cooldown = args.cooldown || 1;
super(pos, size, args);
if (args.prob !== undefined) {
this.prob = args.prob;
} else {
this.prob = 1;
}
this.is_stochastic = this.prob > 0 && this.prob < 1;
if (args.total !== undefined) this.total = args.total;
this.counter = 0;
this.stype = args.stype || null;
this.lastspawntime = -1;
}
update(game, delta) {
super.update(game, delta);
if (!this.stype) return;
if(this.lastspawntime >= Math.round(game.time)) return;
if(Math.round(game.time) % this.cooldown !== 0) return;
const rnd = random.random();
// console.log("spawn ", Math.round(game.time))
if (rnd < this.prob) {
this.lastspawntime = Math.round(game.time);
game._createSprite([this.stype], [this.location.x, this.location.y]);
this.counter++;
}
if (this.total && this.counter >= this.total) {
killSprite(this, undefined, game);
}
}
}
export class RandomNPC extends VGDLSprite {
constructor(pos, size, args) {
args.speed = args.speed || 1;
args.is_stochastic = args.is_stochastic || true;
super(pos, size, args);
}
update(game, delta) {
this.direction = random.choice(BASEDIRS);
super.update(game, delta);
this.physics.activeMovement(this, this.direction);
}
}
export class OrientedSprite extends VGDLSprite {
constructor(pos, size, args) {
super(pos, size, args);
this.draw_arrow = false;
this.orientation = args.orientation || RIGHT;
}
_draw(game) {
// super._draw(this, game);
// if (this.draw_arrow) {
// //TODO: Draw OrientedSprite
// const col = (this.color[0], 255 - this.color[1], this.color[2]);
// // this.gamejs.draw.polygon(game.screen, col, triPoints(this.rect, unitVector(this.orientation)))
// }
}
}
export class Conveyer extends OrientedSprite {
constructor(pos, size, args) {
super(pos, size, args);
this.is_static = true;
// this.color = BLUE;
this.strength = 1;
this.draw_arrow = true;
}
}
export class Missile extends OrientedSprite {
constructor(pos, size, args) {
args.speed ??= 1;
super(pos, size, args);
// this.color = PURPLE;
}
}
export class Switch extends OrientedSprite {
constructor(pos, size, args) {
super(pos, size, args);
this.speed = 1;
// this.color = PURPLE;
}
}
export class OrientedFlicker extends OrientedSprite {
constructor(pos, size, args) {
//Flicker
super(pos, size, args);
this.draw_arrow = true;
this.speed = 0;
this._age = 0;
this.limit = args.limit || 1;
}
update(game, delta) {
super.update(game, delta);
if (this._age > this.limit) killSprite(this, null, game);
this._age++;
}
}
export class Walker extends Missile {
constructor(pos, size, args) {
super(pos, size, args);
this.airsteering = false;
this.is_stochastic = true;
}
update(game, delta) {
if (this.airsteering || this.lastdirection()[0] === 0) {
let d = 0;
if (this.orientation[0] > 0) d = 1;
else if (this.orientation[0] < 0) d = -1;
else d = random.choice([-1, 1]);
this.physics.activeMovement(this, [d, 0]);
}
super.update(game, delta);
}
}
export class WalkJumper extends Walker {
constructor(pos, size, args) {
super(pos, size, args);
this.prob = 0.1;
this.strength = 10;
}
update(game, delta) {
if (this.lastdirection()[0] === 0) {
if (this.prob < random.random())
this.physics.activeMovement(this, (0, -this.strength));
}
super.update(game, delta);
}
}
export class RandomInertial extends RandomNPC {
constructor(pos, size, args) {
super(pos, size, args);
this.physicstype = ContinuousPhysics;
}
}
export class RandomMissile extends Missile {
constructor(pos, size, args) {
super(pos, size, args);
}
}
export class EraticMissile extends Missile {
constructor(pos, size, args) {
super(pos, size, args);
this.prob = args.prob;
this.is_stochastic = this.prob > 0 && this.prob < 1;
}
update(game, delta) {
super.update(game, delta);
if (random.random() < this.prob) this.orientation = random.choice(BASEDIRS);
}
}
export class Bomber extends Missile {
constructor(pos, size, args) {
// Missile
// args.color = args.color || ORANGE;
args.is_static = args.is_static || false;
super(pos, size, args);
if (args.prob !== undefined) {
this.prob = args.prob;
} else {
this.prob = 1;
}
this.is_stochastic = this.prob > 0 && this.prob < 1;
if (args.cooldown !== undefined) {
this.cooldown = args.cooldown;
} else {
this.cooldown = 1;
}
if (args.total !== undefined) this.total = args.total;
this.counter = 0;
this.stype = args.stype;
this.lastspawntime = -1;
}
update(game, delta) {
super.update(game, delta);
if (
this.stype &&
this.lastspawntime < Math.round(game.time) &&
Math.round(game.time) % this.cooldown === 0 &&
random.random() < this.prob
) {
this.lastspawntime = Math.round(game.time);
game._createSprite([this.stype], [this.location.x, this.location.y]);
this.counter++;
}
if (this.total && this.counter >= this.total) {
killSprite(this, undefined, game);
}
}
}
export class Door extends Immovable {
constructor(pos, size, args) {
args.portal = args.portal || true;
super(pos, size, args);
}
}
export class Chaser extends RandomNPC {
constructor(pos, size, args) {
args.portal = args.portal || true;
super(pos, size, args);
this.stype = args.stype;
this.fleeing = false;
}
_closestTargets(game) {
let bestd = 1e100;
let res = [];
game.getSprites(this.stype).forEach((target) => {
const d = this.physics.quickDistance(this, target);
if (d < bestd) {
bestd = d;
res = [target];
} else if (d === bestd) {
res.push(target);
}
});
return res;
}
_movesToward(game, target) {
const res = [];
let basedist = this.physics.quickDistance(this, target);
BASEDIRS.forEach((a) => {
// console.log(a)
let r = { ...this.location };
r.x += a[0];
r.y += a[1];
const newdist = this.physics.quickDistance({ location: r }, target);
// console.log(a, basedist, newdist);
if (this.fleeing && basedist < newdist) {
res.push(a);
} else if (!this.fleeing && basedist > newdist) {
res.push(a);
}
});
return res;
}
update(game, delta) {
let options = [];
const position_options = {};
this._closestTargets(game).forEach((target) => {
options = options.concat(this._movesToward(game, target));
});
if (options.length === 0) {
options = BASEDIRS;
}
// super.update(game, delta);
this.physics.activeMovement(this, options.randomElement());
}
}
export class Fleeing extends Chaser {
constructor(pos, size, args) {
args.portal = args.portal || true;
super(pos, size, args);
this.stype = args.stype;
this.fleeing = true;
}
}
export class BomberRandomMissile extends SpawnPoint {
constructor(pos, size, args) {
super(pos, size, args);
this.stypeMissile = args.stypeMissile.split(",");
}
update(game, delta) {
this.stype = this.stypeMissile.randomElement();
super.update(game, delta);
}
}
export class AStarChaser extends RandomNPC {
constructor(pos, size, args) {
super(pos, size, args);
}
_movesToward = (game, target) => {
const res = [];
const basedist = this.physics.quickDistance(this.location, target.location);
BASEDIRS.forEach((a) => {
let r = { ...this.location };
r.x += a[0];
r.y += a[1];
const newdist = this.physics.quickDistance(r, target.location);
if (this.fleeing && basedist < newdist) res.push(a);
if (!(this.fleeing && basedist > newdist)) res.push(a);
});
return res;
};
}
export class AlternateChaser extends RandomNPC {
constructor(pos, size, args) {
super(pos, size, args);
this.fleeing = false;
this.targets = [];
this.actions = [];
this.stypes1 = args.stype1.split(",");
this.stypes2 = args.stype2.split(",");
}
closestTargets(game, randomTarget) {
this.targets = [];
let best_dist = 10e20;
let num_chasing = 0;
this.stypes1.forEach((s) => {
num_chasing += game.getSprites(s).length;
});
let num_fleeing = 0;
this.stypes2.forEach((s) => {
num_fleeing += game.getSprites(s).length;
});
let target = null;
if (num_chasing > num_fleeing) {
target = this.stypes1.randomElement();
this.fleeing = false;
} else if (num_chasing < num_fleeing) {
target = this.stypes2.randomElement();
this.fleeing = true;
}
if (target === null) return;
const sprites = game.getSprites(target);
for (const sprite of sprites) {
if (randomTarget) {
if (random.random() < this.prob) {
this.targets.push(sprite);
}
} else {
const distance = this.physics.quickDistance(this, sprite);
if (distance < best_dist) {
best_dist = distance;
this.targets = [sprite];
} else if (distance === best_dist) {
this.targets.push(sprite);
}
}
}
}
movesToward(target) {
const distance = this.physics.quickDistance(this, target);
for (const dir of BASEDIRS) {
const new_pos = { ...this.location };
new_pos.x += dir[0];
new_pos.y += dir[1];
const new_dist = this.physics.quickDistance(this, { location: new_pos });
if (this.fleeing && new_dist > distance) {
this.actions.push(dir);
} else if (!this.fleeing && new_dist < distance) {
this.actions.push(dir);
}
}
}
update(game, delta) {
this.lastmove++;
this.actions = [];
this.physics.passiveMovement(this);
this.closestTargets(game, false);
for (const target of this.targets) {
this.movesToward(target);
}
let action = [0, 0];
if (this.actions.length === 0) {
action = BASEDIRS.randomElement();
} else {
action = this.actions.randomElement();
}
this.physics.activeMovement(this, action, this.speed);
}
}
export class PathAltChaser extends AlternateChaser {
constructor(pos, size, args) {
super(pos, size, args);
this.lastTarget = null;
}
update(game, delta) {
this.physics.passiveMovement(this);
let action = [0, 0];
if (
this.lastTarget === null ||
this.physics.quickDistance(this, this.lastTarget) < 1
) {
this.closestTargets(game, false);
} else {
this.targets.push(this.lastTarget);
}
if (!this.fleeing && this.targets.length > 0) {
this.lastTarget = this.targets[0];
} else {
}
this.physics.activeMovement(this, action, this.speed);
}
}
//
// function AStarChaser(gamejs, pos, size, args) {
// this.stype = null;
// this.fleeing = false;
// this.drawpath = null;
// this.walableTiles = null;
// this.neighborNodes =null;
// RandomNPC.call(this, gamejs, pos, size, args);
// }
// AStarChaser.prototype = Object.create(RandomNPC.prototype);
//
// AStarChaser.prototype._movesToward = (game, target) => {
// var res = [];
// var basedist = this.physics.distance(this.rect, target.rect);
// var that = this;
// BASEDIRS.forEach(a => {
// var r = that.rect.copy();
// r = r.move(a);
// var newdist = that.physics.distance(r, target.rect);
// if (that.fleeing && basedist < newdist)
// res.push(a);
// if (!(that.fleeing && basedist > newdist))
// res.push(a);
// });
// return res;
// }
//
// AStarChaser.prototype._draw (game) {
// RandomNPC.prototype._draw.call(this, game);
// if (this.walableTiles) {
// var col = this.gamejs.Color(0, 0, 255, 100);
// var that = this;
// this.walableTiles.forEach(sprite => {
// that.gamejs.draw.rect(game.screen, col, sprite.rect);
// });
// }
//
// if (this.neighborNodes) {
// var col = this.gamejs.Color(0, 255, 255, 80);
// var that = this;
// this.neighborNodes.forEach(node => {
// that.gamejs.draw.rect(game.screen, col, node.sprite.rect);
// })
// }
//
// if (this.drawpath) {
// var col = this.gamejs.Color(0, 255, 0, 120);
// var that = this;
// this.drawpath.slice(1, -1).forEach(sprite => {
// that.gamejs.draw.rect(game.screen, col, sprite.rect);
// });
// }
// }
//
//
// AStarChaser.prototype._setDebugVariables = (world, path) => {
// var path_sprites = path.map(node => {return node.sprite});
//
// this.walableTiles = world.get_walkable_tiles();
// this.neighborNodes = world.neighbor_nodes_of_sprite(this);
// this.drawpath = path_sprits;
// }
//
// AStarChaser.prototype.update (game) {
// VGDLSprite.prototype.update.call(this, game);
// }