From aec8e5b078d69ae60650c18a8e4b1e260f7c518d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Grzegorz=20Ta=C5=84czyk?= Date: Sat, 5 Oct 2024 02:29:52 +0200 Subject: [PATCH] migration to ts --- .../game/{game-world.js => game-world.ts} | 49 ++++++---- .../battle/game/masterplan/soldierplan.ts | 4 +- .../battle/game/objects/object-arrow.ts | 2 +- .../{object-soldier.js => object-soldier.ts} | 94 +++++++++++++------ .../src/screens/battle/util/vmath.js | 13 ++- 5 files changed, 113 insertions(+), 49 deletions(-) rename games/masterplan/src/screens/battle/game/{game-world.js => game-world.ts} (77%) rename games/masterplan/src/screens/battle/game/objects/{object-soldier.js => object-soldier.ts} (77%) diff --git a/games/masterplan/src/screens/battle/game/game-world.js b/games/masterplan/src/screens/battle/game/game-world.ts similarity index 77% rename from games/masterplan/src/screens/battle/game/game-world.js rename to games/masterplan/src/screens/battle/game/game-world.ts index 5efbed22..6ed6e70f 100644 --- a/games/masterplan/src/screens/battle/game/game-world.js +++ b/games/masterplan/src/screens/battle/game/game-world.ts @@ -1,9 +1,16 @@ import { ARROW_RANGE, BALL_RANGE, EDGE_RADIUS, MIN_TICK, UPDATE_TICK } from '../consts'; import { VMath } from '../util/vmath'; +import { GameObject } from './objects/game-object'; import { ArrowObject, ExplosionObject } from './objects/object-arrow'; import { SoldierObject } from './objects/object-soldier'; export class GameWorld { + objects: GameObject[]; + objectsByType: { [key: string]: GameObject[] }; + collisionHandlers: any[]; + edgeRadius: number; + worldTime: number; + constructor() { this.objects = []; this.objectsByType = { @@ -30,7 +37,7 @@ export class GameWorld { return this.edgeRadius; } - addObject(...args) { + addObject(...args: GameObject[]) { for (var object of args) { this.objects.push(object); this.objectsByType[object.getClass()].push(object); @@ -40,14 +47,14 @@ export class GameWorld { /** * @param {GameObject} object */ - removeObject(object) { + removeObject(object: GameObject) { this.objects.splice(this.objects.indexOf(object), 1); } - queryObjects(type, fn) { - return this.objectsByType[type].filter(function (object) { - return !fn || fn(object); - }); + queryObjects(type: string, fn?: (object: T) => boolean): T[] { + return this.objectsByType[type].filter((object) => { + return !fn || fn(object as T); + }) as T[]; } /** @@ -55,11 +62,11 @@ export class GameWorld { * @param elapsedTime how much time elapsed since last update * @return {Number} elapsedTime not consumed */ - update(elapsedTime) { + update(elapsedTime: number) { var deltaTime = Math.min(elapsedTime, MIN_TICK); - this.objects.forEach(function (object) { + this.objects.forEach((object) => { this.updateObject(object, deltaTime / UPDATE_TICK); - }, this); + }); elapsedTime -= deltaTime; this.worldTime += deltaTime; @@ -74,14 +81,14 @@ export class GameWorld { * Collision check */ collisions() { - var hitArrows = this.queryObjects('Arrow', (arrow) => arrow.isHit()); - this.queryObjects('Soldier').forEach(function (soldier) { + var hitArrows = this.queryObjects('Arrow', (arrow: ArrowObject) => arrow.isHit()); + this.queryObjects('Soldier').forEach((soldier) => { if (soldier.life <= 0) { return; } // soldier -> soldier - this.queryObjects('Soldier').forEach((soldierLeft, idxLeft) => { + this.queryObjects('Soldier').forEach((soldierLeft, idxLeft) => { if ( idxLeft <= this.objectsByType['Soldier'].indexOf(soldier) || soldierLeft.life <= 0 || @@ -117,7 +124,7 @@ export class GameWorld { }); } - triggerCollisions(leftObject, rightObject) { + triggerCollisions(leftObject: GameObject, rightObject: GameObject) { this.collisionHandlers.forEach(function (collisionHandler) { if (leftObject instanceof collisionHandler.left && rightObject instanceof collisionHandler.right) { collisionHandler.handler(leftObject, rightObject); @@ -128,7 +135,11 @@ export class GameWorld { }); } - onCollision(leftObjectType, rightObjectType, handler) { + onCollision( + leftObjectType: new (...args: any[]) => L, + rightObjectType: new (...args: any[]) => R, + handler: (left: L, right: R) => void, + ) { this.collisionHandlers.push({ left: leftObjectType, right: rightObjectType, @@ -136,7 +147,7 @@ export class GameWorld { }); } - onSoldierCollision(leftSoldier, rightSoldier) { + onSoldierCollision(leftSoldier: SoldierObject, rightSoldier: SoldierObject) { // soldiers should bounce off each other var distance = VMath.distance(leftSoldier.vec, rightSoldier.vec); if (distance === 0) { @@ -156,7 +167,7 @@ export class GameWorld { } } - onArrowCollision(soldier, arrow) { + onArrowCollision(soldier: SoldierObject, arrow: ArrowObject) { var distance = VMath.distance(arrow.vec, soldier.vec); if (arrow.type === 'arrow') { arrow.hit(soldier, distance); @@ -172,17 +183,17 @@ export class GameWorld { * @param object * @param deltaTime */ - updateObject(object, deltaTime) { + updateObject(object: GameObject, deltaTime: number) { object.update(deltaTime); } getAlive() { - return this.queryObjects('Soldier', (soldier) => soldier.life > 0).reduce( + return this.queryObjects('Soldier', (soldier) => soldier.life > 0).reduce( (r, soldier) => ((r[soldier.color] = (r[soldier.color] || 0) + 1), r), { '#ff0000': 0, '#00ff00': 0, - }, + } as Record, ); } } diff --git a/games/masterplan/src/screens/battle/game/masterplan/soldierplan.ts b/games/masterplan/src/screens/battle/game/masterplan/soldierplan.ts index f8218b2a..d8189f0a 100644 --- a/games/masterplan/src/screens/battle/game/masterplan/soldierplan.ts +++ b/games/masterplan/src/screens/battle/game/masterplan/soldierplan.ts @@ -34,7 +34,7 @@ export class SoldierPlan { return this.currentCommand; } - canClaim(enemy: SoldierObject, soldier: SoldierObject) { + canClaim(enemy: SoldierObject, soldier: SoldierObject): boolean { if (!this.claims[enemy.soldierId]) { this.claims[enemy.soldierId] = []; } @@ -48,6 +48,8 @@ export class SoldierPlan { if (distant.length === this.claims[enemy.soldierId].length) { return true; } + + return false; } unclaim(enemy: SoldierObject, soldier: SoldierObject) { diff --git a/games/masterplan/src/screens/battle/game/objects/object-arrow.ts b/games/masterplan/src/screens/battle/game/objects/object-arrow.ts index b4d8fdee..58f11589 100644 --- a/games/masterplan/src/screens/battle/game/objects/object-arrow.ts +++ b/games/masterplan/src/screens/battle/game/objects/object-arrow.ts @@ -17,7 +17,7 @@ export class ArrowObject extends GameObject { maxDist: number; attackBase: number; type: string; - v: number[]; + v: [number, number]; adir: number | undefined; ay: number | undefined; diff --git a/games/masterplan/src/screens/battle/game/objects/object-soldier.js b/games/masterplan/src/screens/battle/game/objects/object-soldier.ts similarity index 77% rename from games/masterplan/src/screens/battle/game/objects/object-soldier.js rename to games/masterplan/src/screens/battle/game/objects/object-soldier.ts index cf574944..c673910a 100644 --- a/games/masterplan/src/screens/battle/game/objects/object-soldier.js +++ b/games/masterplan/src/screens/battle/game/objects/object-soldier.ts @@ -19,11 +19,50 @@ import { import { updateState } from '../../states'; import { ArrowObject } from './object-arrow'; import { $ } from '../../util/dom'; +import { GameWorld } from '../game-world'; +import { SoldierPlan } from '../masterplan/soldierplan'; +import { Canvas } from '../../util/canvas'; let soldierID = 0; export class SoldierObject extends GameObject { - constructor(x, y, direction, plan, world, color, type) { + soldierId: number; + plan: SoldierPlan; + world: GameWorld; + type: string; + velocity: number; + targetVelocity: number; + force: [number, number]; + targetDirection: number; + image: any; + imageDead: any; + color: string; + life: number; + newLife: number; + defenceCooldown: number; + weight: number; + baseSpeed: number; + seekRange: number = 0; + attackRange: number = 0; + rangeDefence: number = 0; + meleeDefence: number = 0; + meleeAttack: number = 0; + canCharge: boolean = false; + isMelee: boolean = false; + rangeAttack: number | undefined; + rangedCooldown: number | undefined; + rangeType: 'arrow' | 'ball' | undefined; + cooldowns: Record = {}; + enemy: any; + constructor( + x: number, + y: number, + direction: number, + plan: SoldierPlan, + world: GameWorld, + color: string, + type: 'tank' | 'warrior' | 'archer' | 'artillery', + ) { direction += (Math.random() - Math.random()) / 1000; super(x, y, SOLDIER_WIDTH, SOLDIER_HEIGHT, direction); this.soldierId = soldierID++; @@ -108,7 +147,7 @@ export class SoldierObject extends GameObject { /** * @param {Canvas} canvas */ - render(canvas) { + render(canvas: Canvas) { canvas .save() .translate(-this.getWidth() / 2, -this.getHeight() / 2) @@ -116,7 +155,7 @@ export class SoldierObject extends GameObject { if (this.life > 0) { if (this.life < MAX_LIFE) { - canvas.drawText(10, 0, this.life << 0); + canvas.drawText(10, 0, String(this.life << 0)); } canvas.fillStyle(this.color).fillRect(0, 10, 10, (5 * this.life) / MAX_LIFE); } @@ -124,7 +163,7 @@ export class SoldierObject extends GameObject { canvas.restore(); } - cooldown(name, maxValue) { + cooldown(name: string, maxValue: number) { if (!this.cooldowns[name] || this.world.getTime() > this.cooldowns[name]) { this.cooldowns[name] = this.world.getTime() + maxValue; return true; @@ -132,16 +171,16 @@ export class SoldierObject extends GameObject { } // controls - setTargetDirection(targetDirection) { + setTargetDirection(targetDirection: number) { this.targetDirection = targetDirection; } - setTargetVelocity(targetVelocity) { + setTargetVelocity(targetVelocity: number) { this.targetVelocity = targetVelocity * this.baseSpeed; } // update - updateVelocity(deltaTime) { + updateVelocity(deltaTime: number) { this.velocity = this.getTargetVelocity() * deltaTime + this.velocity * (1 - deltaTime); } @@ -153,9 +192,9 @@ export class SoldierObject extends GameObject { return this.velocity; } - update(deltaTime) { + update(deltaTime: number) { if (this.life > 0) { - this.updatePlan(deltaTime); + this.updatePlan(); } else if (this.enemy) { this.setEnemy(null); } @@ -191,12 +230,12 @@ export class SoldierObject extends GameObject { this.life = this.newLife; } - distance(soldier) { + distance(soldier: SoldierObject) { return VMath.distance(this.vec, soldier.vec); } - queryEnemy(distance) { - var enemies = this.world.queryObjects( + queryEnemy(distance: number) { + var enemies = this.world.queryObjects( 'Soldier', (soldier) => soldier.isEnemy(this) && @@ -207,15 +246,16 @@ export class SoldierObject extends GameObject { if (enemies.length > 0) { var fn; if (this.type === 'artillery') { - fn = (r, soldier) => (soldier.distance(this) > r.distance(this) ? soldier : r); + fn = (r: SoldierObject, soldier: SoldierObject) => (soldier.distance(this) > r.distance(this) ? soldier : r); } else { - fn = (r, soldier) => (soldier.distance(this) < r.distance(this) ? soldier : r); + fn = (r: SoldierObject, soldier: SoldierObject) => (soldier.distance(this) < r.distance(this) ? soldier : r); } return enemies.reduce(fn, enemies[0]); } + return null; } - seekEnemy(distance) { + seekEnemy(distance: number) { // are there any enemies? if (this.enemy && (this.enemy.life <= 0 || this.enemy.distance(this) > distance)) { this.setEnemy(null); @@ -241,9 +281,9 @@ export class SoldierObject extends GameObject { this.rangeAttack && dist < this.attackRange && dist > MIN_RANGE_ATTACK && - this.cooldown('arrow', this.rangedCooldown) + this.cooldown('arrow', this.rangedCooldown!) ) { - this.world.addObject(new ArrowObject(this.vec, this.enemy.vec, this.world, this.rangeAttack, this.rangeType)); + this.world.addObject(new ArrowObject(this.vec, this.enemy.vec, this.world, this.rangeAttack, this.rangeType!)); aa.play('arrow'); if (this.type === 'artillery') { this.hitBy(50); @@ -264,22 +304,22 @@ export class SoldierObject extends GameObject { } } - addForce(vec) { + addForce(vec: [number, number]) { this.force = VMath.add(this.force, vec); } - getDefence(soldier, factor) { + getDefence(soldier: SoldierObject | ArrowObject, factor: number) { var baseDefence = Math.abs(VMath.angle(VMath.sub(soldier.vec, this.vec), [Math.cos(this.direction), Math.sin(this.direction)])) / Math.PI; return Math.min(baseDefence / factor, 0.3); } - getAttack(soldier) { + getAttack(soldier: SoldierObject) { return (1 - this.getDefence(soldier, this.meleeDefence)) * this.meleeAttack; } - hit(bySoldier) { + hit(bySoldier: SoldierObject) { var damage = (this.cooldown('defence', this.defenceCooldown) ? this.getDefence(bySoldier, this.meleeDefence) : 1) * bySoldier.getAttack(this); @@ -289,13 +329,13 @@ export class SoldierObject extends GameObject { aa.play('damage'); } - hitByArrow(arrow, distance) { + hitByArrow(arrow: ArrowObject, distance: number) { var damage; if (arrow.type === 'ball') { - damage = arrow.getAttack(arrow) / Math.pow(distance, 1 / 4); + damage = arrow.getAttack() / Math.pow(distance, 1 / 4); } else { damage = - arrow.getAttack(arrow) * + arrow.getAttack() * (this.cooldown('defence', this.defenceCooldown) ? this.getDefence(arrow, this.rangeDefence) : 1); } this.hitBy(damage); @@ -303,11 +343,11 @@ export class SoldierObject extends GameObject { aa.play('hitarrow'); } - hitBy(value) { + hitBy(value: number) { this.newLife = Math.max(this.newLife - value, 0); } - isEnemy(ofSoldier) { + isEnemy(ofSoldier: SoldierObject) { return this.plan.masterPlan !== ofSoldier.plan.masterPlan; } @@ -315,7 +355,7 @@ export class SoldierObject extends GameObject { return 'Soldier'; } - setEnemy(enemy) { + setEnemy(enemy: SoldierObject | null) { if (this.enemy) { this.plan.unclaim(this.enemy, this); this.enemy = null; diff --git a/games/masterplan/src/screens/battle/util/vmath.js b/games/masterplan/src/screens/battle/util/vmath.js index 9e3fbb8e..58c343aa 100644 --- a/games/masterplan/src/screens/battle/util/vmath.js +++ b/games/masterplan/src/screens/battle/util/vmath.js @@ -42,7 +42,12 @@ export const VMath = { atan2: function (A, B) { return Math.atan2(B[1] - A[1], B[0] - A[0]); }, - + /** + * + * @param {[number, number]} A + * @param {[number, number]} B + * @returns {[number, number]} + */ sub: function (A, B) { return [A[0] - B[0], A[1] - B[1]]; }, @@ -55,6 +60,12 @@ export const VMath = { add: function (A, B) { return [A[0] + B[0], A[1] + B[1]]; }, + /** + * + * @param {[number, number]} V + * @param {number} s + * @returns {[number, number]} + */ scale: function (V, s) { return [V[0] * s, V[1] * s]; },