Scope admin tower to its co-op route and balance range
This commit is contained in:
1 parent
a536b698f7
commit
8777f28595
5 files changed
+44
-11
No files matched your search
@@ -57,7 +57,7 @@ var DIFFICULTIES = [
|
||||
];
|
||||
|
||||
function defaultBalanceConfig() {
|
||||
return {schema:2,towers:JSON.parse(JSON.stringify(TOWERS)),enemies:JSON.parse(JSON.stringify(ENEMIES))};
|
||||
return {schema:3,towers:JSON.parse(JSON.stringify(TOWERS)),enemies:JSON.parse(JSON.stringify(ENEMIES)),admin_tower:{range:650,cooldown:0.08}};
|
||||
}
|
||||
function boundedNumber(value, fallback, minimum, maximum, integer) {
|
||||
var number = Number(value);
|
||||
@@ -93,7 +93,12 @@ function sanitizeBalanceConfig(input) {
|
||||
shieldResistance:boundedNumber(candidate.shieldResistance,base.shieldResistance,0.01,1,false)
|
||||
};
|
||||
});
|
||||
return {schema:2,towers:towers,enemies:enemies};
|
||||
var adminInput=input.admin_tower||{};
|
||||
var adminTower={
|
||||
range:boundedNumber(adminInput.range,defaults.admin_tower.range,80,1200,false),
|
||||
cooldown:boundedNumber(adminInput.cooldown,defaults.admin_tower.cooldown,0.04,20,false)
|
||||
};
|
||||
return {schema:3,towers:towers,enemies:enemies,admin_tower:adminTower};
|
||||
}
|
||||
function readBalanceConfig(nk) {
|
||||
var record = nk && typeof nk.storageRead === "function" ? readStorageRecord(nk,BALANCE_COLLECTION,BALANCE_KEY,SYSTEM_USER_ID) : null;
|
||||
@@ -108,6 +113,8 @@ function readBalanceConfig(nk) {
|
||||
var legacyBoss = storedConfig.enemies[5] || {};
|
||||
if (Number(legacyTower.damage) === 20) config.towers[6].damage = 22;
|
||||
if (Number(legacyBoss.hp) === 740) config.enemies[5].hp = 620;
|
||||
}
|
||||
if (Number(storedConfig.schema || 1) < 3) {
|
||||
updatedAt = Date.now();
|
||||
if (nk && typeof nk.storageWrite === "function") {
|
||||
try { nk.storageWrite([{collection:BALANCE_COLLECTION,key:BALANCE_KEY,userId:SYSTEM_USER_ID,value:{config:config,updated_at:updatedAt},permissionRead:0,permissionWrite:0,version:record.version||"*"}]); }
|
||||
@@ -574,7 +581,10 @@ function matchLeave(ctx,logger,nk,dispatcher,tick,state,presences) {
|
||||
}
|
||||
function parseMessage(nk,message) { try { return JSON.parse(nk.binaryToString(message.data)||"{}"); } catch (error) { return null; } }
|
||||
function towerInfo(state,type) {
|
||||
if (type===ADMIN_TOWER_TYPE) return {cost:0,damage:1000000,range:3000,cooldown:0.08,targetClass:"both",bothScale:1};
|
||||
if (type===ADMIN_TOWER_TYPE) {
|
||||
var admin=state.balance&&state.balance.admin_tower||defaultBalanceConfig().admin_tower;
|
||||
return {cost:0,damage:1000000,range:admin.range,cooldown:admin.cooldown,targetClass:"both",bothScale:1};
|
||||
}
|
||||
return state.balance && state.balance.towers[type] || TOWERS[type];
|
||||
}
|
||||
function enemyInfo(state,type) { return state.balance && state.balance.enemies[type] || ENEMIES[type]; }
|
||||
@@ -659,6 +669,7 @@ function spawnEnemy(state) {
|
||||
state.enemies.push({id:state.nextEnemyId++,type:type,x:route[0][0],y:route[0][1],path_index:1,route_id:routeId,hp:base.hp*hullScale,max_hp:base.hp*hullScale,speed:base.speed*(1+state.wave*0.012)*(boss?0.82:1)*d.speed,reward:Math.floor((base.reward+state.wave)*(boss?4:1)*d.income),angle:0,boss:boss,shield:hasShield,shield_hp:maxShieldHp,max_shield_hp:maxShieldHp,shield_resistance:base.shieldResistance});
|
||||
}
|
||||
function applyDamageToEnemy(enemy,damage,towerType) {
|
||||
if (towerType===ADMIN_TOWER_TYPE) { enemy.shield=false; enemy.shield_hp=0; enemy.hp=0; return; }
|
||||
var applied=damage;
|
||||
if (enemy.shield) {
|
||||
if (towerType===1) {
|
||||
@@ -688,6 +699,7 @@ function updateTowers(state,delta) {
|
||||
var t=state.towers[i],tower=towerInfo(state,t.type); t.cooldown-=delta; var range=towerRange(state,t),targetIndex=-1,best=-1,bestShield=false;
|
||||
for (var j=0;j<state.enemies.length;j++) {
|
||||
var candidate=state.enemies[j];
|
||||
if (t.type===ADMIN_TOWER_TYPE&&candidate.route_id!==(t.x<MIRROR_X?0:1)) continue;
|
||||
if ((tower.targetClass==="both"||tower.targetClass===enemyInfo(state,candidate.type).category)&&distanceSquared(t.x,t.y,candidate.x,candidate.y)<=range*range) {
|
||||
var p=enemyProgress(candidate),shieldPriority=t.type===1&&candidate.shield===true;
|
||||
if (targetIndex<0||(shieldPriority&&!bestShield)||(shieldPriority===bestShield&&p>best)) { best=p; bestShield=shieldPriority; targetIndex=j; }
|
||||
|
||||
@@ -44,10 +44,13 @@ assert.strictEqual(adminBalance.config.towers[0].damage, 22);
|
||||
const editedBalance = JSON.parse(JSON.stringify(adminBalance.config));
|
||||
editedBalance.towers[0].damage = 31;
|
||||
editedBalance.enemies[0].hp = 90;
|
||||
editedBalance.admin_tower.range = 700;
|
||||
editedBalance.admin_tower.cooldown = 0.12;
|
||||
assert.throws(() => context.rpcSetGameBalance({ userId: "pilot-b" }, {}, balanceNk, JSON.stringify({ config: editedBalance })), /server-to-server/);
|
||||
assert.strictEqual(JSON.parse(context.rpcSetGameBalance({}, {}, balanceNk, JSON.stringify({ config: editedBalance }))).accepted, true);
|
||||
assert.strictEqual(JSON.parse(context.rpcGetGameBalance({ userId: "pilot-b" }, {}, balanceNk, "{}")).config.towers[0].damage, 31);
|
||||
assert.strictEqual(context.matchInit({}, {}, balanceNk, { difficulty: 1 }).state.balance.enemies[0].hp, 90);
|
||||
assert.strictEqual(context.matchInit({}, {}, balanceNk, { difficulty: 1 }).state.balance.admin_tower.range, 700);
|
||||
assert.throws(() => context.rpcSetGameBalance({}, {}, balanceNk, JSON.stringify({ config: { towers: [], enemies: [] } })), /invalid balance schema/);
|
||||
const legacyStorage = new Map();
|
||||
const legacyKey = `game_balance:${context.SYSTEM_USER_ID}:global`;
|
||||
@@ -63,11 +66,12 @@ const migrationNk = {
|
||||
storageWrite(ops) { for (const op of ops) legacyStorage.set(`${op.collection}:${op.userId}:${op.key}`, { ...op, version: "8" }); }
|
||||
};
|
||||
const migratedBalance = context.readBalanceConfig(migrationNk);
|
||||
assert.strictEqual(migratedBalance.config.schema, 2);
|
||||
assert.strictEqual(migratedBalance.config.schema, 3);
|
||||
assert.strictEqual(migratedBalance.config.towers[0].damage, 31, "Balance migrations must preserve unrelated admin tuning.");
|
||||
assert.strictEqual(migratedBalance.config.towers[6].damage, 22, "Unchanged legacy Flak defaults should migrate to the new value.");
|
||||
assert.strictEqual(migratedBalance.config.enemies[5].hp, 620, "Unchanged legacy boss HP should migrate to the new value.");
|
||||
assert.strictEqual(legacyStorage.get(legacyKey).value.config.schema, 2, "Migrated balance should persist for future matches.");
|
||||
assert.strictEqual(migratedBalance.config.admin_tower.range, 650, "Older balance settings should receive the safer admin range.");
|
||||
assert.strictEqual(legacyStorage.get(legacyKey).value.config.schema, 3, "Migrated balance should persist for future matches.");
|
||||
const hostId = "11111111-1111-1111-1111-111111111111";
|
||||
const friendId = "22222222-2222-2222-2222-222222222222";
|
||||
const sentInvites = [];
|
||||
@@ -401,11 +405,14 @@ assert.ok(state.towers.some(tower => tower.type === 7 && tower.owner === 0), "Au
|
||||
const adminCombatState = {
|
||||
balance: state.balance,
|
||||
towers: [{ id: 99, type: 7, x: 2200, y: 600, owner: 0, level: 1, cooldown: 0, angle: 0 }],
|
||||
enemies: [{ id: 99, type: 8, x: 2250, y: 600, hp: 100000, max_hp: 100000, shield: true, shield_hp: 1000, shield_resistance: 0.28 }],
|
||||
enemies: [
|
||||
{ id: 99, type: 8, x: 2250, y: 600, route_id: 1, path_index: 1, hp: 100000, max_hp: 100000, shield: true, shield_hp: 1000, shield_resistance: 0.28 },
|
||||
{ id: 100, type: 8, x: 2250, y: 610, route_id: 0, path_index: 1, hp: 100000, max_hp: 100000, shield: true, shield_hp: 1000, shield_resistance: 0.28 }
|
||||
],
|
||||
shots: [], credits: [0, 0]
|
||||
};
|
||||
context.updateTowers(adminCombatState, 0.1);
|
||||
assert.strictEqual(adminCombatState.enemies.length, 0, "One admin turret shot should destroy even a shielded enemy.");
|
||||
assert.deepStrictEqual(adminCombatState.enemies.map(enemy => enemy.id), [100], "The admin turret must one-shot shielded enemies only on its own route.");
|
||||
|
||||
state = context.matchLoop({}, logger, nk, dispatcher, 57, state, [message(presenceA, 13, {})]).state;
|
||||
assert.strictEqual(state.wave, 1);
|
||||
|
||||
Reference in new issue
Block a user