From f8c443f3c9b4a2fecbf2b6ce70d0ff9f2b3e8c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Wed, 26 Apr 2017 01:27:42 +0200 Subject: [PATCH] Fixed encounter generation matching star system level --- TODO | 3 +-- src/core/Battle.ts | 6 ++--- src/core/Equipment.spec.ts | 14 +++++------ src/core/Equipment.ts | 8 +++--- src/core/FleetGenerator.ts | 6 ++--- src/core/LootGenerator.ts | 24 ++++++++++++++++-- src/core/LootTemplate.ts | 20 +++++++++++++++ src/core/Player.ts | 31 +++++++----------------- src/core/Ship.ts | 11 ++++++--- src/core/ShipGenerator.spec.ts | 4 +-- src/core/ShipGenerator.ts | 23 +++++++++--------- src/core/ShipModel.ts | 12 ++++++--- src/core/Slot.ts | 2 +- src/core/StarLocation.ts | 2 +- src/ui/battle/ArenaShip.ts | 2 +- src/ui/battle/ShipListItem.ts | 2 +- src/ui/character/CharacterFleetMember.ts | 2 +- src/ui/map/FleetDisplay.ts | 2 +- 18 files changed, 104 insertions(+), 70 deletions(-) diff --git a/TODO b/TODO index 7bce4a9..2174f7d 100644 --- a/TODO +++ b/TODO @@ -4,7 +4,7 @@ * Character sheet: improve eye-catching for shop and loot section * Character sheet: highlight allowed destinations during drag-and-drop * Add permanent effects to ship models to ease balancing -* Ships should start battle in formation to force them to move +* Find incentives to move from starting position * Fix targetting not resetting when using action shortcuts * Add battle statistics and/or critics in outcome dialog * Add battle experience @@ -40,7 +40,6 @@ * TacticalAI: add pauses to not play too quickly * TacticalAI: replace BullyAI * Map: restore fog of war -* Map: a star system should have an average level * Map: add information on current star/location + information on hovered location * Map: generate random shops * Map: remove jump links that cross the radius of other systems diff --git a/src/core/Battle.ts b/src/core/Battle.ts index 9c9c78b..58f6e7e 100644 --- a/src/core/Battle.ts +++ b/src/core/Battle.ts @@ -50,9 +50,9 @@ module TS.SpaceTac { } // Create a quick random battle, for testing purposes - static newQuickRandom(start = true): Battle { - var player1 = Player.newQuickRandom("John"); - var player2 = Player.newQuickRandom("Carl"); + static newQuickRandom(start = true, level = 1): Battle { + var player1 = Player.newQuickRandom("John", level, true); + var player2 = Player.newQuickRandom("Carl", level, true); var result = new Battle(player1.fleet, player2.fleet); if (start) { diff --git a/src/core/Equipment.spec.ts b/src/core/Equipment.spec.ts index 3515f46..1b4b21a 100644 --- a/src/core/Equipment.spec.ts +++ b/src/core/Equipment.spec.ts @@ -15,32 +15,32 @@ module TS.SpaceTac.Specs { var equipment = new Equipment(); var ship = new Ship(); - expect(equipment.canBeEquipped(ship)).toBe(true); + expect(equipment.canBeEquipped(ship.attributes)).toBe(true); equipment.requirements["skill_time"] = 2; - expect(equipment.canBeEquipped(ship)).toBe(false); + expect(equipment.canBeEquipped(ship.attributes)).toBe(false); ship.attributes.skill_time.set(1); - expect(equipment.canBeEquipped(ship)).toBe(false); + expect(equipment.canBeEquipped(ship.attributes)).toBe(false); ship.attributes.skill_time.set(2); - expect(equipment.canBeEquipped(ship)).toBe(true); + expect(equipment.canBeEquipped(ship.attributes)).toBe(true); ship.attributes.skill_time.set(3); - expect(equipment.canBeEquipped(ship)).toBe(true); + expect(equipment.canBeEquipped(ship.attributes)).toBe(true); // Second requirement equipment.requirements["skill_material"] = 3; - expect(equipment.canBeEquipped(ship)).toBe(false); + expect(equipment.canBeEquipped(ship.attributes)).toBe(false); ship.attributes.skill_material.set(4); - expect(equipment.canBeEquipped(ship)).toBe(true); + expect(equipment.canBeEquipped(ship.attributes)).toBe(true); }); it("generates a description of the effects", function () { diff --git a/src/core/Equipment.ts b/src/core/Equipment.ts index 89c7f39..c3a76cc 100644 --- a/src/core/Equipment.ts +++ b/src/core/Equipment.ts @@ -112,19 +112,19 @@ module TS.SpaceTac { } /** - * Returns true if the equipment can be equipped on a ship. + * Returns true if the equipment can be equipped on a ship with given skills. * - * This checks *requirements* against the ship skills. + * This checks *requirements* against the skills. * * This does not check where the equipment currently is (except if is it already attached and should be detached first). */ - canBeEquipped(ship: Ship): boolean { + canBeEquipped(skills: ShipAttributes): boolean { if (this.attached_to) { return false; } else { var able = true; iteritems(this.requirements, (attr, minvalue) => { - if (ship.getAttribute(attr) < minvalue) { + if (skills[attr].get() < minvalue) { able = false; } }); diff --git a/src/core/FleetGenerator.ts b/src/core/FleetGenerator.ts index aba98f5..4be6db3 100644 --- a/src/core/FleetGenerator.ts +++ b/src/core/FleetGenerator.ts @@ -9,13 +9,13 @@ module TS.SpaceTac { } // Generate a fleet of a given level - generate(level: number, player?: Player, ship_count = 3): Fleet { + generate(level: number, player?: Player, ship_count = 3, upgrade = false): Fleet { var fleet = new Fleet(player); var ship_generator = new ShipGenerator(this.random); while (ship_count--) { - var ship = ship_generator.generate(level); - ship.name = "Ship " + ship_count.toString(); + var ship = ship_generator.generate(level, null, upgrade); + ship.name = `${fleet.player.name}'s Level ${ship.level.get()} ${ship.model.name}`; fleet.addShip(ship); } diff --git a/src/core/LootGenerator.ts b/src/core/LootGenerator.ts index 2e967f7..960ca93 100644 --- a/src/core/LootGenerator.ts +++ b/src/core/LootGenerator.ts @@ -31,8 +31,6 @@ module TS.SpaceTac { this.templates = templates; } - // TODO Add generator from skills - // Generate a random equipment for a specific level // If slot is specified, it will generate an equipment for this slot type specifically // If no equipment could be generated from available templates, null is returned @@ -48,5 +46,27 @@ module TS.SpaceTac { // Pick a random equipment return this.random.choice(equipments); } + + /** + * Generate a random equipment of highest level, from a given set of skills + */ + generateHighest(skills: ShipSkills, quality = EquipmentQuality.COMMON, slot: SlotType | null = null): Equipment | null { + let templates = this.templates.filter(template => slot == null || slot == template.slot); + let candidates: Equipment[] = []; + let level = 1; + + templates.forEach(template => { + let equipment = template.generateHighest(skills, quality, this.random); + if (equipment && equipment.level >= level) { + if (equipment.level > level) { + candidates.splice(0); + level = equipment.level; + } + candidates.push(equipment); + } + }); + + return (candidates.length == 0) ? null : this.random.choice(candidates); + } } } diff --git a/src/core/LootTemplate.ts b/src/core/LootTemplate.ts index 04f18eb..c73fccb 100644 --- a/src/core/LootTemplate.ts +++ b/src/core/LootTemplate.ts @@ -213,6 +213,26 @@ module TS.SpaceTac { return result; } + /** + * Generate the highest equipment level, for a given set of skills + */ + generateHighest(skills: ShipSkills, quality = EquipmentQuality.COMMON, random = RandomGenerator.global): Equipment | null { + let level = 1; + let equipment: Equipment | null = null; + let attributes = new ShipAttributes(); + keys(skills).forEach(skill => attributes[skill].set(skills[skill].get())); + do { + let nequipment = this.generate(level, quality, random); + if (nequipment.canBeEquipped(attributes)) { + equipment = nequipment; + } else { + break; + } + level += 1; + } while (level < 100); + return equipment; + } + /** * Set skill requirements that will be added to each level of equipment. */ diff --git a/src/core/Player.ts b/src/core/Player.ts index 9d1e9ca..69ec261 100644 --- a/src/core/Player.ts +++ b/src/core/Player.ts @@ -1,6 +1,9 @@ module TS.SpaceTac { // One player (human or IA) export class Player { + // Player's name + name: string; + // Universe in which we are playing universe: Universe; @@ -11,33 +14,17 @@ module TS.SpaceTac { visited: StarLocation[] = []; // Create a player, with an empty fleet - constructor(universe: Universe = new Universe()) { + constructor(universe: Universe = new Universe(), name = "Player") { this.universe = universe; + this.name = name; this.fleet = new Fleet(this); } // Create a quick random player, with a fleet, for testing purposes - static newQuickRandom(name: String): Player { - var player = new Player(new Universe()); - var ship: Ship; - var ship_generator = new ShipGenerator(); - - ship = ship_generator.generate(1); - ship.name = name + "'s First"; - player.fleet.addShip(ship); - - ship = ship_generator.generate(1); - ship.name = name + "'s Second"; - player.fleet.addShip(ship); - - ship = ship_generator.generate(1); - ship.name = name + "'s Third"; - player.fleet.addShip(ship); - - ship = ship_generator.generate(1); - ship.name = name + "'s Fourth"; - player.fleet.addShip(ship); - + static newQuickRandom(name: string, level = 1, upgrade = false): Player { + let player = new Player(new Universe(), name); + let generator = new FleetGenerator(); + player.fleet = generator.generate(level, player, 4, upgrade); return player; } diff --git a/src/core/Ship.ts b/src/core/Ship.ts index ce4fa5f..59ead07 100644 --- a/src/core/Ship.ts +++ b/src/core/Ship.ts @@ -65,7 +65,7 @@ module TS.SpaceTac { name: string // Code of the ShipModel used to create it - model: string + model: ShipModel // Flag indicating if the ship is alive alive: boolean @@ -100,10 +100,9 @@ module TS.SpaceTac { play_priority = 0; // Create a new ship inside a fleet - constructor(fleet: Fleet | null = null, name = "Ship") { + constructor(fleet: Fleet | null = null, name = "Ship", model = new ShipModel("default", "Default", 1, 0)) { this.fleet = fleet || new Fleet(); this.name = name; - this.model = "default"; this.alive = true; this.sticky_effects = []; this.slots = []; @@ -115,6 +114,10 @@ module TS.SpaceTac { if (fleet) { fleet.addShip(this); } + + this.model = model; + this.setCargoSpace(model.cargo); + model.slots.forEach(slot => this.addSlot(slot)); } // Returns true if the ship is able to play @@ -532,7 +535,7 @@ module TS.SpaceTac { canEquip(item: Equipment): Slot | null { let free_slot = first(this.slots, slot => slot.type == item.slot_type && !slot.attached); if (free_slot) { - if (item.canBeEquipped(this)) { + if (item.canBeEquipped(this.attributes)) { return free_slot; } else { return null; diff --git a/src/core/ShipGenerator.spec.ts b/src/core/ShipGenerator.spec.ts index 9957894..30383b6 100644 --- a/src/core/ShipGenerator.spec.ts +++ b/src/core/ShipGenerator.spec.ts @@ -2,9 +2,9 @@ module TS.SpaceTac.Specs { describe("ShipGenerator", function () { it("can use ship model", function () { var gen = new ShipGenerator(); - var model = new ShipModel("test", 1, 2, SlotType.Shield, SlotType.Weapon, SlotType.Weapon); + var model = new ShipModel("test", "Test", 1, 2, SlotType.Shield, SlotType.Weapon, SlotType.Weapon); var ship = gen.generate(1, model); - expect(ship.model).toBe("test"); + expect(ship.model).toBe(model); expect(ship.cargo_space).toBe(2); expect(ship.slots.length).toBe(3); expect(ship.slots[0].type).toBe(SlotType.Shield); diff --git a/src/core/ShipGenerator.ts b/src/core/ShipGenerator.ts index 80d17f8..2b9d237 100644 --- a/src/core/ShipGenerator.ts +++ b/src/core/ShipGenerator.ts @@ -10,28 +10,29 @@ module TS.SpaceTac { // Generate a ship of a given level // The ship will not be named, nor will be a member of any fleet - generate(level: number, model: ShipModel | null = null): Ship { - var result = new Ship(); - var loot = new LootGenerator(this.random); - + generate(level: number, model: ShipModel | null = null, upgrade = false): Ship { if (!model) { // Get a random model model = ShipModel.getRandomModel(level, this.random); } - // Apply model - result.model = model.code; - result.setCargoSpace(model.cargo); - model.slots.forEach((slot: SlotType) => { - result.addSlot(slot); - }); + var result = new Ship(null, undefined, model); + var loot = new LootGenerator(this.random); // Set all skills to 1 (to be able to use at least basic equipment) keys(result.skills).forEach(skill => result.upgradeSkill(skill)); + // Level upgrade + result.level.forceLevel(level); + if (upgrade) { + while (result.getAvailableUpgradePoints() > 0) { + result.upgradeSkill(this.random.choice(keys(SHIP_SKILLS))); + } + } + // Fill equipment slots result.slots.forEach((slot: Slot) => { - var equipment = loot.generate(level, EquipmentQuality.COMMON, slot.type); + var equipment = loot.generateHighest(result.skills, EquipmentQuality.COMMON, slot.type); if (equipment) { slot.attach(equipment) if (slot.attached !== equipment) { diff --git a/src/core/ShipModel.ts b/src/core/ShipModel.ts index bf7b190..0195c8b 100644 --- a/src/core/ShipModel.ts +++ b/src/core/ShipModel.ts @@ -5,6 +5,9 @@ module TS.SpaceTac { // Code to identify the model code: string; + // Human-readable model name + name: string; + // Minimal level to use this model level: number; @@ -14,8 +17,9 @@ module TS.SpaceTac { // Available slots slots: SlotType[]; - constructor(code: string, level: number, cargo: number, ...slots: SlotType[]) { + constructor(code: string, name: string, level: number, cargo: number, ...slots: SlotType[]) { this.code = code; + this.name = name; this.level = level; this.cargo = cargo; this.slots = slots; @@ -26,12 +30,12 @@ module TS.SpaceTac { // TODO Store in cache var result: ShipModel[] = []; - result.push(new ShipModel("scout", 1, 2, SlotType.Hull, SlotType.Power, SlotType.Power, SlotType.Engine, SlotType.Weapon)); + result.push(new ShipModel("scout", "Scout", 1, 2, SlotType.Hull, SlotType.Power, SlotType.Power, SlotType.Engine, SlotType.Weapon)); - result.push(new ShipModel("whirlwind", 1, 4, SlotType.Hull, SlotType.Shield, SlotType.Power, SlotType.Engine, + result.push(new ShipModel("whirlwind", "Whirlwind", 1, 4, SlotType.Hull, SlotType.Shield, SlotType.Power, SlotType.Engine, SlotType.Weapon, SlotType.Weapon)); - result.push(new ShipModel("tomahawk", 1, 6, SlotType.Hull, SlotType.Shield, SlotType.Power, SlotType.Engine, SlotType.Engine, + result.push(new ShipModel("tomahawk", "Tomahawk", 1, 6, SlotType.Hull, SlotType.Shield, SlotType.Power, SlotType.Engine, SlotType.Engine, SlotType.Weapon)); return result; diff --git a/src/core/Slot.ts b/src/core/Slot.ts index 7a9589f..e0919e4 100644 --- a/src/core/Slot.ts +++ b/src/core/Slot.ts @@ -28,7 +28,7 @@ module TS.SpaceTac { // Attach an equipment in this slot attach(equipment: Equipment): Equipment { - if (this.type === equipment.slot_type && equipment.canBeEquipped(this.ship)) { + if (this.type === equipment.slot_type && equipment.canBeEquipped(this.ship.attributes)) { this.attached = equipment; equipment.attached_to = this; diff --git a/src/core/StarLocation.ts b/src/core/StarLocation.ts index ed7a520..76f6be9 100644 --- a/src/core/StarLocation.ts +++ b/src/core/StarLocation.ts @@ -70,7 +70,7 @@ module TS.SpaceTac { if (this.encounter_random.random() < 0.8) { var fleet_generator = new FleetGenerator(this.encounter_random); var ship_count = this.encounter_random.randInt(1, 5); - this.encounter = fleet_generator.generate(this.star.level, undefined, ship_count); + this.encounter = fleet_generator.generate(this.star.level, undefined, ship_count, true); } } diff --git a/src/ui/battle/ArenaShip.ts b/src/ui/battle/ArenaShip.ts index 8b1b678..205e795 100644 --- a/src/ui/battle/ArenaShip.ts +++ b/src/ui/battle/ArenaShip.ts @@ -34,7 +34,7 @@ module TS.SpaceTac.UI { this.enemy = this.ship.getPlayer() != this.battleview.player; // Add ship sprite - this.sprite = new Phaser.Button(this.game, 0, 0, "ship-" + ship.model + "-sprite"); + this.sprite = new Phaser.Button(this.game, 0, 0, "ship-" + ship.model.code + "-sprite"); this.sprite.rotation = ship.arena_angle; this.sprite.anchor.set(0.5, 0.5); this.sprite.scale.set(64 / this.sprite.width); diff --git a/src/ui/battle/ShipListItem.ts b/src/ui/battle/ShipListItem.ts index 9c54a11..e930b2d 100644 --- a/src/ui/battle/ShipListItem.ts +++ b/src/ui/battle/ShipListItem.ts @@ -38,7 +38,7 @@ module TS.SpaceTac.UI { this.active_effects = new Phaser.Group(this.game); this.addChild(this.active_effects); - this.layer_portrait = new Phaser.Image(this.game, 8, 8, "ship-" + ship.model + "-portrait", 0); + this.layer_portrait = new Phaser.Image(this.game, 8, 8, "ship-" + ship.model.code + "-portrait", 0); this.layer_portrait.scale.set(0.3, 0.3); this.addChild(this.layer_portrait); diff --git a/src/ui/character/CharacterFleetMember.ts b/src/ui/character/CharacterFleetMember.ts index a10109f..56139e0 100644 --- a/src/ui/character/CharacterFleetMember.ts +++ b/src/ui/character/CharacterFleetMember.ts @@ -16,7 +16,7 @@ module TS.SpaceTac.UI { this.sheet = sheet; this.ship = ship; - let portrait_pic = new Phaser.Image(this.game, 0, 0, `ship-${ship.model}-portrait`); + let portrait_pic = new Phaser.Image(this.game, 0, 0, `ship-${ship.model.code}-portrait`); portrait_pic.anchor.set(0.5, 0.5); this.addChild(portrait_pic); diff --git a/src/ui/map/FleetDisplay.ts b/src/ui/map/FleetDisplay.ts index 863205a..4fae1ba 100644 --- a/src/ui/map/FleetDisplay.ts +++ b/src/ui/map/FleetDisplay.ts @@ -25,7 +25,7 @@ module TS.SpaceTac.UI { fleet.ships.forEach((ship, index) => { let offset = LOCATIONS[index]; - let sprite = this.game.add.image(offset[0], offset[1] + 150, `ship-${ship.model}-sprite`, 0, this); + let sprite = this.game.add.image(offset[0], offset[1] + 150, `ship-${ship.model.code}-sprite`, 0, this); sprite.scale.set(64 / sprite.width); sprite.anchor.set(0.5, 0.5); });