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
@@ -206,7 +206,7 @@ var TOWER_DATA := [
|
||||
{"name": "EMP-PULS", "cost": 210, "damage": 20.0, "range": 215.0, "rate": 0.92, "shot_speed": 540.0, "damage_type": "EMP-FLÄCHE 118 · ALLE", "target_class": "both", "both_damage_scale": 1.0, "color": Color("77e8ff"), "unlock_cost": 24, "description": "Impuls trifft alle Luft- und Bodenziele im Radius mit 20 Schaden."},
|
||||
{"name": "RAILGUN", "cost": 300, "damage": 77.0, "range": 330.0, "rate": 1.85, "shot_speed": 1450.0, "damage_type": "PIERCING-STRAHL", "target_class": "both", "both_damage_scale": 0.70, "color": Color("c86bff"), "unlock_cost": 30, "description": "Lila Durchschlagsstrahl trifft alle Gegner auf seiner Schusslinie."},
|
||||
{"name": "FLAK", "cost": 210, "damage": 22.0, "range": 230.0, "rate": 0.42, "shot_speed": 920.0, "damage_type": "LUFT-EINZELZIEL", "target_class": "air", "color": Color("ff737c"), "unlock_cost": 0, "description": "Schnelle Abfangkanone gegen gegnerische Luftschiffe."},
|
||||
{"name": "ADMIN-TURM", "cost": 0, "damage": 1000000.0, "range": 3000.0, "rate": 0.08, "shot_speed": 3000.0, "damage_type": "TESTMODUS", "target_class": "both", "color": Color("fff2a1"), "unlock_cost": 0, "description": "Entwicklertest: besiegt jedes Ziel mit einem Schuss."}
|
||||
{"name": "ADMIN-TURM", "cost": 0, "damage": 1000000.0, "range": 650.0, "rate": 0.08, "shot_speed": 3000.0, "damage_type": "TESTMODUS", "target_class": "both", "color": Color("fff2a1"), "unlock_cost": 0, "description": "Entwicklertest: besiegt jedes Ziel mit einem Schuss."}
|
||||
]
|
||||
|
||||
var ENEMY_DATA := [
|
||||
@@ -638,6 +638,10 @@ func _apply_balance_config(config: Dictionary) -> bool:
|
||||
enemy["reward"] = clampi(int(values.get("reward", enemy.reward)), 0, 50000)
|
||||
enemy["breach_damage"] = clampi(int(values.get("breachDamage", enemy.breach_damage)), 1, 100)
|
||||
enemy["shield_resistance"] = clampf(float(values.get("shieldResistance", enemy.shield_resistance)), 0.01, 1.0)
|
||||
var admin_config = config.get("admin_tower", {})
|
||||
if admin_config is Dictionary:
|
||||
TOWER_DATA[ADMIN_TOWER_TYPE]["range"] = clampf(float(admin_config.get("range", 650.0)), 80.0, 1200.0)
|
||||
TOWER_DATA[ADMIN_TOWER_TYPE]["rate"] = clampf(float(admin_config.get("cooldown", 0.08)), 0.04, 20.0)
|
||||
return true
|
||||
|
||||
|
||||
@@ -936,6 +940,8 @@ func _find_target(tower: Dictionary) -> Dictionary:
|
||||
var best_is_shielded := false
|
||||
var effective_range := _tower_range(tower.type, tower.level)
|
||||
for enemy in enemies:
|
||||
if coop_mode and int(tower.type) == ADMIN_TOWER_TYPE and int(enemy.get("route_id", 0)) != (0 if tower.pos.x < COOP_MIRROR_X else 1):
|
||||
continue
|
||||
if _tower_can_target(tower.type, int(enemy.type)) and tower.pos.distance_to(enemy.pos) <= effective_range:
|
||||
var route: PackedVector2Array = _enemy_route(enemy)
|
||||
var progress: float = enemy.path_index * 1000.0 - enemy.pos.distance_to(route[enemy.path_index])
|
||||
@@ -1042,6 +1048,14 @@ func _update_projectiles(delta: float) -> void:
|
||||
func _apply_damage(enemy: Dictionary, amount: float, owner := 0, weapon_type := -1) -> void:
|
||||
if not enemies.has(enemy):
|
||||
return
|
||||
if weapon_type == ADMIN_TOWER_TYPE:
|
||||
enemy.shield = false
|
||||
enemy.shield_hp = 0.0
|
||||
enemy.hp = 0.0
|
||||
_award_credits(owner, int(enemy.reward))
|
||||
_spawn_burst(enemy.pos, Color("ff6b3d"), 11 + enemy.type * 4)
|
||||
enemies.erase(enemy)
|
||||
return
|
||||
var applied_damage := amount
|
||||
if bool(enemy.get("shield", false)):
|
||||
if weapon_type == 1:
|
||||
|
||||
@@ -6,7 +6,7 @@ RequestExecutionLevel user
|
||||
!include "LogicLib.nsh"
|
||||
|
||||
!define PRODUCT_NAME "Void Bastion"
|
||||
!define PRODUCT_VERSION "0.1.0-alpha.14"
|
||||
!define PRODUCT_VERSION "0.1.0-alpha.15"
|
||||
!define PRODUCT_PUBLISHER "GerGames"
|
||||
!define PRODUCT_WEBSITE "https://void-bastion.gergames.de/"
|
||||
!define PRODUCT_EXE "Void-Bastion.exe"
|
||||
@@ -21,7 +21,7 @@ BrandingText "GerGames"
|
||||
ShowInstDetails show
|
||||
ShowUninstDetails show
|
||||
|
||||
VIProductVersion "0.1.0.14"
|
||||
VIProductVersion "0.1.0.15"
|
||||
VIAddVersionKey /LANG=1031 "ProductName" "${PRODUCT_NAME}"
|
||||
VIAddVersionKey /LANG=1031 "CompanyName" "${PRODUCT_PUBLISHER}"
|
||||
VIAddVersionKey /LANG=1031 "FileDescription" "${PRODUCT_NAME} Setup"
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
|
||||
[application]
|
||||
|
||||
config/name="Void Bastion"
|
||||
config/version="0.1.0-alpha.14"
|
||||
config/version="0.1.0-alpha.15"
|
||||
run/main_scene="res://main.tscn"
|
||||
config/features=PackedStringArray("4.7")
|
||||
config/icon="res://assets/app_icon.png"
|
||||
|
||||
@@ -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