diff --git a/data/advancewars-artwork.json b/data/advancewars-artwork.json index cf82118..d20851a 100644 --- a/data/advancewars-artwork.json +++ b/data/advancewars-artwork.json @@ -14,7 +14,7 @@ }, "unitSheet": { "key": "advancewars-units", - "path": null, + "path": "assets/images/advancewars/advancewars-units.png", "frameWidth": 48, "frameHeight": 64 }, diff --git a/data/advancewars-campaign.json b/data/advancewars-campaign.json index 5da82e1..7bc6c38 100644 --- a/data/advancewars-campaign.json +++ b/data/advancewars-campaign.json @@ -19,7 +19,7 @@ }, "aiProfile": { "skill": 1, - "aggression": 0.4, + "aggression": 0.25, "captureWeight": 0.0 }, "briefing": [ @@ -41,7 +41,12 @@ { "speaker": "ethel", "mood": "idle", - "text": "Deary, just remember: tanks are strong, artillery shoots from afar, and infantry are brave little things. Destroy every enemy unit and the day is ours." + "text": "Oh, and dear — see that empty city in the middle of the field? Walk infantry onto it and choose Capture to claim it. It won't win the day, but every property we hold pays the bills." + }, + { + "speaker": "ethel", + "mood": "idle", + "text": "Now remember: tanks are strong, artillery shoots from afar, and infantry are brave little things. Destroy every enemy unit and the day is ours." } ], "victoryLine": { @@ -62,7 +67,7 @@ "w.q..rr..c.w", "....mrr.....", "..rrrrrrrr..", - "....w..m....", + "....wc.m....", ".rrrrrrrrrr.", ".c.......q..", "....mm......", @@ -163,8 +168,12 @@ "aiProfile": { "skill": 1, "aggression": 0.3, - "captureWeight": 0.6 + "captureWeight": 0.5 }, + "unlockedUnits": [ + ["infantry", "mech", "recon", "tank", "apc", "artillery"], + ["infantry", "mech", "recon", "tank", "apc", "artillery"] + ], "briefing": [ { "speaker": "ethel", @@ -290,8 +299,8 @@ "type": "rout" }, "aiProfile": { - "skill": 2, - "aggression": 0.5, + "skill": 1, + "aggression": 0.35, "captureWeight": 0.0 }, "briefing": [ @@ -453,10 +462,14 @@ "type": "hq" }, "aiProfile": { - "skill": 2, - "aggression": 0.4, - "captureWeight": 0.3 + "skill": 1, + "aggression": 0.35, + "captureWeight": 0.25 }, + "unlockedUnits": [ + ["infantry", "mech", "recon", "tank", "apc", "artillery", "mdtank"], + ["infantry", "mech", "recon", "tank", "apc", "artillery"] + ], "briefing": [ { "speaker": "jeff", @@ -477,6 +490,11 @@ "speaker": "jeff", "mood": "idle", "text": "Come on over whenever you're ready. I'll be here. Sipping lemonade. Aiming." + }, + { + "speaker": "ethel", + "mood": "idle", + "text": "One more thing, dear \u2014 headquarters is releasing the MD Tank to your motor pool. It costs more to build, but it hits harder and shrugs off return fire. Perfect for cracking a dug-in position like Jeff's." } ], "victoryLine": { @@ -656,10 +674,14 @@ "type": "rout" }, "aiProfile": { - "skill": 3, - "aggression": 0.5, - "captureWeight": 0.4 + "skill": 2, + "aggression": 0.4, + "captureWeight": 0.35 }, + "unlockedUnits": [ + ["infantry", "mech", "recon", "tank", "apc", "artillery", "mdtank", "rockets", "antiair"], + ["infantry", "mech", "recon", "tank", "apc", "artillery", "mdtank"] + ], "briefing": [ { "speaker": "brad", @@ -675,6 +697,11 @@ "speaker": "natasha", "mood": "idle", "text": "Then we finish this before the snow falls. All units \u2014 forward!" + }, + { + "speaker": "ethel", + "mood": "idle", + "text": "Before you head out: two new units are up for grabs. Rockets fire clear across the field without ever being fired back at, and the Anti-Air truck keeps our tanks safe from anything that flies. Build wisely." } ], "victoryLine": { @@ -804,10 +831,14 @@ "type": "rout" }, "aiProfile": { - "skill": 3, - "aggression": 0.45, + "skill": 2, + "aggression": 0.4, "captureWeight": 0.3 }, + "unlockedUnits": [ + ["infantry", "mech", "recon", "tank", "apc", "artillery", "mdtank", "rockets", "antiair", "missiles"], + ["infantry", "mech", "recon", "tank", "apc", "artillery", "mdtank", "rockets", "antiair"] + ], "briefing": [ { "speaker": "ethel", @@ -823,6 +854,11 @@ "speaker": "maurice", "mood": "idle", "text": "Can't see a thing out there... Bucky says use recon units. Bucky's smart. Big vision range on those." + }, + { + "speaker": "ethel", + "mood": "idle", + "text": "And dear, we've cleared the Missile Launcher for production. It hits just as far as rockets, but its crew keeps a much longer eye on things \u2014 perfect for punching through all this fog." } ], "victoryLine": { @@ -942,8 +978,12 @@ "aiProfile": { "skill": 2, "aggression": 0.4, - "captureWeight": 0.4 + "captureWeight": 0.35 }, + "unlockedUnits": [ + ["infantry", "mech", "recon", "tank", "apc", "artillery", "mdtank", "rockets", "antiair", "missiles", "lander", "cruiser"], + ["infantry", "mech", "recon", "tank", "apc", "artillery", "mdtank", "rockets", "antiair", "missiles"] + ], "briefing": [ { "speaker": "schooner", @@ -964,6 +1004,11 @@ "speaker": "ethel", "mood": "idle", "text": "Landers carry two land units across the sea and unload on those pale shoal tiles. Guard them \u2014 they can't fight back!" + }, + { + "speaker": "ethel", + "mood": "idle", + "text": "Oh \u2014 and both the Lander and the Cruiser are yours to build now, not just borrow. Keep that channel escorted and the beachhead is ours." } ], "victoryLine": { @@ -1145,8 +1190,8 @@ "type": "rout" }, "aiProfile": { - "skill": 3, - "aggression": 0.55, + "skill": 2, + "aggression": 0.45, "captureWeight": 0.0 }, "briefing": [ @@ -1285,6 +1330,10 @@ "aggression": 0.5, "captureWeight": 0.4 }, + "unlockedUnits": [ + ["infantry", "mech", "recon", "tank", "mdtank", "apc", "artillery", "rockets", "antiair", "missiles", "fighter", "bomber", "bcopter", "tcopter", "battleship", "cruiser", "lander", "sub"], + ["infantry", "mech", "recon", "tank", "apc", "artillery", "mdtank", "rockets", "antiair", "missiles", "lander", "cruiser"] + ], "briefing": [ { "speaker": "schooner", @@ -1300,6 +1349,11 @@ "speaker": "ethel", "mood": "idle", "text": "You can win on land or at sea, dear \u2014 but don't ignore either. A battleship parked off your coast ruins everyone's morning." + }, + { + "speaker": "ethel", + "mood": "idle", + "text": "Last thing, dear \u2014 the whole armory's open to you now. Battleships, submarines, even our air wing are standing by. Whatever the enemy throws at us, we can answer in kind." } ], "victoryLine": { diff --git a/src/games/advancewars/AdvanceWarsGame.js b/src/games/advancewars/AdvanceWarsGame.js index 8637747..15a4d28 100644 --- a/src/games/advancewars/AdvanceWarsGame.js +++ b/src/games/advancewars/AdvanceWarsGame.js @@ -138,6 +138,7 @@ export default class AdvanceWarsGame extends Phaser.Scene { enemyCos: [meta.cfg.enemyCo], fog: meta.cfg.fog, production: true, + unlockedUnits: undefined, startFunds: [3000, 3000], objective: { type: 'rout' }, dayLimit: 60, @@ -157,6 +158,7 @@ export default class AdvanceWarsGame extends Phaser.Scene { cos: [mission.playerCo, ...mission.enemyCos], fog: mission.fog, production: mission.production, + unlockedUnits: mission.unlockedUnits, startFunds: mission.startFunds, objective: mission.objective, dayLimit: mission.dayLimit, diff --git a/src/games/advancewars/AdvanceWarsLogic.js b/src/games/advancewars/AdvanceWarsLogic.js index de78bad..7dbec3c 100644 --- a/src/games/advancewars/AdvanceWarsLogic.js +++ b/src/games/advancewars/AdvanceWarsLogic.js @@ -57,6 +57,7 @@ export function createGame(rules, mapDef, opts = {}) { turn: 0, fog: !!opts.fog, production: opts.production !== false, + unlockedUnits: opts.unlockedUnits, rngState: (opts.seed ?? 1) >>> 0, w, h, terrain, @@ -685,6 +686,8 @@ export function applyAction(state, rules, action) { if (!t.builds || state.owner[tileKey(state, x, y)] !== army) return fail('not your factory'); const domain = spec.domain === 'air' ? 'air' : spec.domain === 'sea' ? 'sea' : 'land'; if (t.builds !== domain) return fail('wrong factory type'); + const allowed = state.unlockedUnits?.[army]; + if (allowed && !allowed.includes(unitType)) return fail('unit not unlocked'); if (unitAt(state, x, y)) return fail('tile occupied'); const fx = coEffects(rules, state, army); const cost = Math.round(spec.cost * (fx.costMult ?? 1)); @@ -922,8 +925,10 @@ export function buildOptions(rules, state, x, y) { const army = state.turn; if (!t.builds || state.owner[tileKey(state, x, y)] !== army || unitAt(state, x, y)) return []; const fx = coEffects(rules, state, army); + const allowed = state.unlockedUnits?.[army]; return rules.units .filter((u) => (u.domain === 'air' ? 'air' : u.domain === 'sea' ? 'sea' : 'land') === t.builds) + .filter((u) => !allowed || allowed.includes(u.id)) .map((u) => ({ type: u.id, cost: Math.round(u.cost * (fx.costMult ?? 1)), diff --git a/tools/verifyAdvanceWars.js b/tools/verifyAdvanceWars.js index 19da54e..46cdea1 100644 --- a/tools/verifyAdvanceWars.js +++ b/tools/verifyAdvanceWars.js @@ -691,7 +691,7 @@ console.log('Campaign data'); try { state = Logic.createGame(rules, m.map, { cos: [m.playerCo, ...m.enemyCos], - fog: m.fog, production: m.production, + fog: m.fog, production: m.production, unlockedUnits: m.unlockedUnits, startFunds: m.startFunds, objective: m.objective, dayLimit: m.dayLimit, seed: 42, }); } catch (err) { @@ -796,7 +796,7 @@ console.log('AI'); for (let i = 0; i < tries; i++) { const state = Logic.createGame(rules, m.map, { cos: [m.playerCo, ...m.enemyCos], - fog: m.fog, production: m.production, startFunds: m.startFunds, + fog: m.fog, production: m.production, unlockedUnits: m.unlockedUnits, startFunds: m.startFunds, objective: m.objective, dayLimit: m.dayLimit, seed: 500 + i, }); let guard = (m.dayLimit ?? 60) * (1 + m.enemyCos.length) + 8;