Skip to content

Commit d4740bd

Browse files
c86ec23b-fef1-4979-b2fa-b9adc351b8ccCopilotCopilotabcxff
authored
Players can now spawn from factories (#185)
* factory test * Update Arena.ts * Fix boss death notif * broadcast msg function * whoops * typescript * safety * fix crash * update color properly * admin fun cmd * tag cleanup * Crash fix * speedup AI slightly * move getRandomPosition to util * Fix maze wall team * factory spawn chance tweak * Fix spawns * Fix 4 teams base locations * typo * fix randompos for circles Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address code review feedback: remove unused imports, fix validation, add missing factory spawn (#187) * Initial plan * Address review feedback: fix typos, remove unused imports, use isFinite, add factory spawning to Domination Co-authored-by: abcxff <79597906+abcxff@users.noreply.github.com> * Remove redundant MAX_SAFE_INTEGER checks with isFinite Co-authored-by: abcxff <79597906+abcxff@users.noreply.github.com> * Add back bounds checking for score and points in commands --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: abcxff <79597906+abcxff@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: abcxff <79597906+abcxff@users.noreply.github.com>
1 parent 902f169 commit d4740bd

23 files changed

Lines changed: 334 additions & 175 deletions

src/Client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,11 +324,11 @@ export default class Client {
324324
this.setHasCheated(true);
325325

326326
player.destroy();
327-
player.onKill(player);
328327
player.onDeath(player);
328+
player.onKill(player);
329329
}
330330
}
331-
331+
332332
return;
333333
}
334334
case ServerBound.Spawn: {

src/Const/Commands.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,36 @@
1818

1919
import Client from "../Client"
2020
import { AccessLevel, maxPlayerLevel } from "../config";
21+
22+
import ObjectEntity from "../Entity/Object";
23+
import LivingEntity from "../Entity/Live";
24+
2125
import AbstractBoss from "../Entity/Boss/AbstractBoss";
2226
import Defender from "../Entity/Boss/Defender";
2327
import FallenBooster from "../Entity/Boss/FallenBooster";
2428
import FallenOverlord from "../Entity/Boss/FallenOverlord";
2529
import Guardian from "../Entity/Boss/Guardian";
2630
import Summoner from "../Entity/Boss/Summoner";
27-
import LivingEntity from "../Entity/Live";
31+
2832
import ArenaCloser from "../Entity/Misc/ArenaCloser";
2933
import FallenAC from "../Entity/Misc/Boss/FallenAC";
30-
import Mothership from "../Entity/Misc/Mothership";
3134
import FallenSpike from "../Entity/Misc/Boss/FallenSpike";
3235
import FallenMegaTrapper from "../Entity/Misc/Boss/FallenMegaTrapper";
36+
37+
import Mothership from "../Entity/Misc/Mothership";
3338
import Dominator from "../Entity/Misc/Dominator";
34-
import ObjectEntity from "../Entity/Object";
39+
3540
import AbstractShape from "../Entity/Shape/AbstractShape";
3641
import Crasher from "../Entity/Shape/Crasher";
3742
import Pentagon from "../Entity/Shape/Pentagon";
3843
import Square from "../Entity/Shape/Square";
3944
import Triangle from "../Entity/Shape/Triangle";
45+
4046
import AutoTurret from "../Entity/Tank/AutoTurret";
4147
import Bullet from "../Entity/Tank/Projectile/Bullet";
4248
import TankBody from "../Entity/Tank/TankBody";
49+
50+
import { TeamEntity } from "../Entity/Misc/TeamEntity";
4351
import { AIState } from "../Entity/AI";
4452
import { Entity, EntityStateFlags } from "../Native/Entity";
4553
import { saveToVLog } from "../util";
@@ -60,6 +68,7 @@ export const enum CommandID {
6068
gameGodmode = "game_godmode",
6169
gameAnnounce = "game_announce",
6270
gameGoldenName = "game_golden_name",
71+
gameNeutral = "game_neutral",
6372
adminSummon = "admin_summon",
6473
adminKillAll = "admin_kill_all",
6574
adminKillEntity = "admin_kill_entity",
@@ -154,6 +163,12 @@ export const commandDefinitions = {
154163
description: "Toggles the golden nickname color that appears upon using cheats",
155164
permissionLevel: AccessLevel.FullAccess,
156165
isCheat: false
166+
},
167+
game_neutral: {
168+
id: CommandID.gameNeutral,
169+
description: "Sets your tank's team to the neutral team",
170+
permissionLevel: AccessLevel.FullAccess,
171+
isCheat: false
157172
},
158173
admin_summon: {
159174
id: CommandID.adminSummon,
@@ -194,23 +209,23 @@ export const commandCallbacks = {
194209
game_set_level: (client: Client, levelArg: string) => {
195210
const level = parseInt(levelArg);
196211
const player = client.camera?.cameraData.player;
197-
if (isNaN(level) || !Entity.exists(player) || !TankBody.isTank(player)) return;
212+
if (!isFinite(level) || !Entity.exists(player) || !TankBody.isTank(player)) return;
198213
const finalLevel = client.accessLevel == AccessLevel.FullAccess ? level : Math.min(maxPlayerLevel, level);
199214
client.camera?.setLevel(finalLevel);
200215
},
201216
game_set_score: (client: Client, scoreArg: string) => {
202217
const score = parseInt(scoreArg);
203218
const camera = client.camera?.cameraData;
204219
const player = client.camera?.cameraData.player;
205-
if (isNaN(score) || score > Number.MAX_SAFE_INTEGER || score < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
220+
if (!isFinite(score) || score > Number.MAX_SAFE_INTEGER || score < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
206221
camera.score = score;
207222
},
208223
game_set_stat_max: (client: Client, statIdArg: string, statMaxArg: string) => {
209224
const statId = StatCount - parseInt(statIdArg);
210225
const statMax = parseInt(statMaxArg);
211226
const camera = client.camera?.cameraData;
212227
const player = client.camera?.cameraData.player;
213-
if (statId < 0 || statId >= StatCount || isNaN(statId) || isNaN(statMax) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
228+
if (statId < 0 || statId >= StatCount || !isFinite(statId) || !isFinite(statMax) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
214229
const clampedStatMax = Math.max(statMax, 0);
215230
camera.statLimits[statId as Stat] = clampedStatMax;
216231
camera.statLevels[statId as Stat] = Math.min(camera.statLevels[statId as Stat], clampedStatMax);
@@ -220,22 +235,27 @@ export const commandCallbacks = {
220235
const statPoints = parseInt(statPointsArg);
221236
const camera = client.camera?.cameraData;
222237
const player = client.camera?.cameraData.player;
223-
if (statId < 0 || statId >= StatCount || isNaN(statId) || isNaN(statPoints) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
238+
if (statId < 0 || statId >= StatCount || !isFinite(statId) || !isFinite(statPoints) || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
224239
camera.statLevels[statId as Stat] = statPoints;
225240
},
226241
game_add_upgrade_points: (client: Client, pointsArg: string) => {
227242
const points = parseInt(pointsArg);
228243
const camera = client.camera?.cameraData;
229244
const player = client.camera?.cameraData.player;
230-
if (isNaN(points) || points > Number.MAX_SAFE_INTEGER || points < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
245+
if (!isFinite(points) || points > Number.MAX_SAFE_INTEGER || points < Number.MIN_SAFE_INTEGER || !Entity.exists(player) || !TankBody.isTank(player) || !camera) return;
231246
camera.statsAvailable += points;
232247
},
233248
game_teleport: (client: Client, xArg: string, yArg: string) => {
234249
const player = client.camera?.cameraData.player;
235250
if (!Entity.exists(player) || !ObjectEntity.isObject(player)) return;
251+
252+
if (!xArg || !yArg) return;
253+
236254
const x = xArg.match(RELATIVE_POS_REGEX) ? player.positionData.x + parseInt(xArg.slice(1) || "0", 10) : parseInt(xArg, 10);
237255
const y = yArg.match(RELATIVE_POS_REGEX) ? player.positionData.y + parseInt(yArg.slice(1) || "0", 10) : parseInt(yArg, 10);
238-
if (isNaN(x) || isNaN(y)) return;
256+
257+
if (!isFinite(x) || !isFinite(y)) return;
258+
239259
player.positionData.x = x;
240260
player.positionData.y = y;
241261
player.setVelocity(0, 0);
@@ -293,6 +313,15 @@ export const commandCallbacks = {
293313
game_golden_name: (client: Client, activeArg?: string) => {
294314
client.setHasCheated(!client.hasCheated());
295315
},
316+
game_neutral: (client: Client) => {
317+
const team = client.camera?.game.arena;
318+
const player = client.camera?.cameraData.values.player;
319+
320+
if (!team || !player) return;
321+
if (!ObjectEntity.isObject(player)) return;
322+
323+
TeamEntity.setTeam(team, player);
324+
},
296325
admin_summon: (client: Client, entityArg: string, countArg?: string, xArg?: string, yArg?: string) => {
297326
const count = countArg ? parseInt(countArg) : 1;
298327
let x = parseInt(xArg || "0", 10);
@@ -326,11 +355,11 @@ export const commandCallbacks = {
326355
["Triangle", Triangle]
327356
] as [string, typeof ObjectEntity][]).get(entityArg);
328357

329-
if (isNaN(count) || count < 0 || !game || !TEntity) return;
358+
if (!isFinite(count) || count < 0 || !game || !TEntity) return;
330359

331360
for (let i = 0; i < count; ++i) {
332361
const boss = new TEntity(game);
333-
if (!isNaN(x) && !isNaN(y)) {
362+
if (isFinite(x) && isFinite(y)) {
334363
boss.positionData.x = x;
335364
boss.positionData.y = y;
336365
}

src/Entity/AI.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export class AI {
8888
/** The current game. */
8989
public game: GameServer;
9090
/** The AI's target. */
91-
public target: Entity & { positionData: PositionGroup, physicsData: PhysicsGroup, relationsData: RelationsGroup, velocity: Vector } | null = null;
91+
public target: ObjectEntity | null = null;
9292
/** The speed at which the ai's owner can move. */
9393
public movementSpeed = 1;
9494
/** The speed at which the ai can reach the target. */
@@ -168,9 +168,9 @@ export class AI {
168168
chunk ^= bitValue;
169169
const id = 32 * i + bitIdx;
170170

171-
const entity = this.game.entities.inner[id] as ObjectEntity;
171+
const entity = this.game.entities.inner[id];
172172
if (!entity || entity.hash === 0) continue;
173-
if (!entity.positionData || !entity.relationsData || !entity.physicsData) continue;
173+
if (!ObjectEntity.isObject(entity)) continue;
174174

175175
if (!entity.isPhysical) continue;
176176
// Check if the target is living
@@ -197,7 +197,7 @@ export class AI {
197197
}
198198
}
199199

200-
return this.target = closestEntity as Entity & { positionData: PositionGroup, physicsData: PhysicsGroup, relationsData: RelationsGroup, velocity: Vector };
200+
return this.target = closestEntity;
201201
}
202202

203203
/** Aims and predicts at the target. */

src/Entity/Boss/AbstractBoss.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,10 @@ export default class AbstractBoss extends LivingEntity {
163163
* Will set game.arena.boss to null, so that the next boss can spawn
164164
*/
165165
public onDeath(killer: LivingEntity) {
166-
let killerName: string;
166+
let killerName: string = "an unnamed tank";
167167

168168
if (TankBody.isTank(killer) || AbstractBoss.isBoss(killer)) {
169-
killerName = killer.nameData.values.name;
170-
} else {
171-
killerName = "an unnamed tank";
169+
killerName = killer.nameData.values.name || "an unnamed tank";
172170
}
173171

174172
this.game.broadcast()

src/Entity/Misc/MazeWall.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,17 @@
1616
along with this program. If not, see <https://www.gnu.org/licenses/>
1717
*/
1818

19-
import GameServer from "../../Game";
19+
import ArenaEntity from "../../Native/Arena";
2020
import ObjectEntity from "../Object";
2121

2222
import { PhysicsFlags, Color } from "../../Const/Enums";
23+
2324
/**
2425
* Only used for maze walls and nothing else.
2526
*/
2627
export default class MazeWall extends ObjectEntity {
2728
public static newFromBounds(
28-
game: GameServer,
29+
arena: ArenaEntity,
2930
minX: number,
3031
minY: number,
3132
maxX: number,
@@ -40,11 +41,11 @@ export default class MazeWall extends ObjectEntity {
4041
const centerX = (minX + maxX) / 2;
4142
const centerY = (minY + maxY) / 2;
4243

43-
return new MazeWall(game, centerX, centerY, width, height);
44+
return new MazeWall(arena, centerX, centerY, width, height);
4445
}
4546

46-
public constructor(game: GameServer, x: number, y: number, width: number, height: number) {
47-
super(game);
47+
public constructor(arena: ArenaEntity, x: number, y: number, width: number, height: number) {
48+
super(arena.game);
4849

4950
this.setGlobalEntity();
5051

@@ -58,7 +59,7 @@ export default class MazeWall extends ObjectEntity {
5859
this.physicsData.values.pushFactor = 2;
5960
this.physicsData.values.absorbtionFactor = 0;
6061

61-
this.relationsData.values.team = this.game.arena;
62+
this.relationsData.values.team = arena;
6263

6364
this.styleData.values.borderWidth = 10;
6465
this.styleData.values.color = Color.Box;

src/Entity/Misc/Mothership.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export default class Mothership extends TankBody {
8989
this.game.broadcast()
9090
.u8(ClientBound.Notification)
9191
// If mothership has a team name, use it, otherwise just say has destroyed a mothership
92-
.stringNT(`${killerTeamIsATeam ? (killerTeam.teamName || "a mysterious group") : (killer.nameData?.values.name || "an unnamed tank")} has destroyed ${teamIsATeam ? team.teamName + "'s" : "a"} Mothership!`)
92+
.stringNT(`${killerTeamIsATeam ? (killerTeam.teamName || "a mysterious group") : (killer.nameData?.values.name || "an unnamed tank")} has destroyed ${teamIsATeam ? (team.teamName || "a mysterious group") + "'s" : "a"} Mothership!`)
9393
.u32(killerTeamIsATeam ? ColorsHexCode[killerTeam.teamData.values.teamColor] : 0x000000)
9494
.float(-1)
9595
.stringNT("").send();

src/Entity/Misc/TeamBase.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import GameServer from "../../Game";
2020

2121
import { HealthFlags, PhysicsFlags, StyleFlags } from "../../Const/Enums";
22-
import { TeamGroupEntity } from "./TeamEntity";
22+
import { TeamEntity, TeamGroupEntity } from "./TeamEntity";
2323
import LivingEntity from "../Live";
2424
import BaseDrones from "./BaseDrones";
2525
/**
@@ -34,6 +34,8 @@ export default class TeamBase extends LivingEntity {
3434

3535
this.relationsData.values.team = team;
3636

37+
if (team instanceof TeamEntity) team.base = this;
38+
3739
this.positionData.values.x = x;
3840
this.positionData.values.y = y;
3941

src/Entity/Misc/TeamEntity.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
*/
1818

1919
import GameServer from "../../Game";
20+
import TeamBase from "./TeamBase";
21+
import ObjectEntity from "../Object";
22+
import TankBody from "../Tank/TankBody";
2023

2124
import { Color } from "../../Const/Enums";
2225
import { Entity } from "../../Native/Entity";
@@ -51,6 +54,9 @@ export class TeamEntity extends Entity implements TeamGroupEntity {
5154

5255
/** Used for notifications in team based gamemodes */
5356
public teamName: string;
57+
58+
/** The team's spawn base. */
59+
public base: TeamBase | null = null;
5460

5561
public constructor(game: GameServer, color: Color, name: string = ColorsTeamName[color] || "UNKNOWN") {
5662
super(game);
@@ -64,4 +70,16 @@ export class TeamEntity extends Entity implements TeamGroupEntity {
6470

6571
return !!entity.teamData;
6672
}
73+
74+
public static setTeam(team: TeamGroupEntity, entity: ObjectEntity) {
75+
if (!Entity.exists(entity)) return;
76+
77+
entity.relationsData.values.team = team;
78+
79+
entity.styleData.color = team.teamData.values.teamColor;
80+
81+
if (TankBody.isTank(entity)) {
82+
entity.cameraEntity.relationsData.values.team = team;
83+
}
84+
}
6785
}

src/Entity/Object.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,11 @@ export default class ObjectEntity extends Entity {
9595
/** Used to determine the parent of all parents. */
9696
public rootParent: ObjectEntity = this;
9797

98-
/** Entity tags. */
98+
/** Entity bit flag tags. */
9999
public entityTags: number = 0;
100100

101101
/** Entity type ID. */
102-
public arenaMobID: string = ""
102+
public arenaMobID: string | null = null;
103103

104104
/** Velocity used for physics. */
105105
public velocity = new Vector();
@@ -231,7 +231,11 @@ export default class ObjectEntity extends Entity {
231231

232232
if (this.physicsData.values.flags & PhysicsFlags.showsOnMap) {
233233
const globalEntities = this.game.entities.globalEntities;
234-
util.removeFast(globalEntities, globalEntities.indexOf(this.id));
234+
const id = this.id;
235+
236+
if (!globalEntities.includes(id)) return;
237+
238+
util.removeFast(globalEntities, globalEntities.indexOf(id));
235239
}
236240

237241
super.delete();

0 commit comments

Comments
 (0)