1
0
Fork 0

Fixed encounter generation matching star system level

This commit is contained in:
Michaël Lemaire 2017-04-26 01:27:42 +02:00
parent 8ba518270b
commit f8c443f3c9
18 changed files with 104 additions and 70 deletions

3
TODO
View file

@ -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

View file

@ -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) {

View file

@ -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 () {

View file

@ -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(<keyof ShipAttributes>attr) < minvalue) {
if (skills[attr].get() < minvalue) {
able = false;
}
});

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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.
*/

View file

@ -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;
}

View file

@ -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;

View file

@ -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);

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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);
});