1
0
Fork 0

Added skill upgrading

This commit is contained in:
Michaël Lemaire 2017-03-17 01:07:00 +01:00
parent 18daed4c72
commit 9c358113db
18 changed files with 236 additions and 48 deletions

View file

@ -23,6 +23,14 @@ After making changes to sources, you need to recompile:
## Ships
### Level and experience
A ship gains experience during battles. When reaching a certain amount of experience points,
a ship will automatically level up (which is, gain 1 level).
A ship starts at level 1. There is no upper limit to level value (except 99, for display sake,
but it may not be reached in a classic campaign).
### In-combat values (HSP)
In combat, a ship's vitals are represented by the HSP system (Hull-Shield-Power):
@ -55,8 +63,6 @@ or a temporary effect caused by a weapon or a drone).
For example, a ship that equips a power generator with "power recovery +3", but has a sticky effect
of "power recovery -1" from a previous weapon hit, will have an effective power recovery of 2.
Attributes may also be upgraded permanently during level up.
### Skills
Skills represent a ship's ability to use equipments:
@ -72,7 +78,8 @@ Each equipment has minimal skill requirements to be used. For example, a weapon
and "energy >= 3" to be equipped. A ship that does not meet these requirements will not be able to use
the equipment.
Like for attributes, skill values are controlled by equipments, effects and level up.
Skills are defined by the player, using points given while leveling up.
As for attributes, skill values may also be altered by equipments.
If an equipped item has a requirement of "time skill >= 2", that the ship has "time skill" of exactly 2, and
that a temporary effect of "time skill -1" is active, the requirement is no longer fulfilled and the equipped

3
TODO
View file

@ -1,9 +1,10 @@
* UI: Use a common component class, and a layer abstraction
* Character sheet: allow item moving to another ship
* Character sheet: add tooltips (on values, slots and equipments)
* Character sheet: add levelling up (spending of available points) + initial character creation
* Character sheet: add initial character creation
* Character sheet: disable interaction during battle (except for loot screen)
* Add battle statistics and/or critics in outcome dialog
* Add battle experience and feedback on level up
* Ensure that tweens and particle emitters get destroyed once animation is done (or view changes)
* Highlight ships that will be included as target of current action
* Controls: Do not focus on ship while targetting for area effects (dissociate hover and target)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -1 +1 @@
Subproject commit fb3268b8ef0c8f6e32c5cf10e896b9a134d45f6a
Subproject commit 2b11ca44c3dcf368baa9099467842750aa176be7

View file

@ -9,8 +9,8 @@ module TS.SpaceTac.Specs {
fleet2.addShip(new Ship());
fleet2.addShip(new Ship());
fleet2.ships[2].level = 5;
fleet2.ships[3].level = 5;
fleet2.ships[2].level.forceLevel(5);
fleet2.ships[3].level.forceLevel(5);
fleet2.ships[0].addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon, "0a"));
fleet2.ships[0].addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon, "0b"));

View file

@ -28,7 +28,7 @@ module TS.SpaceTac {
var luck = random.random();
if (luck > 0.9) {
// Salvage a supposedly transported item
var transported = this.generateLootItem(random, ship.level);
var transported = this.generateLootItem(random, ship.level.get());
if (transported) {
this.loot.push(transported);
}

View file

@ -8,9 +8,9 @@ module TS.SpaceTac {
fleet.addShip(new Ship());
fleet.addShip(new Ship());
fleet.ships[0].level = 2;
fleet.ships[1].level = 4;
fleet.ships[2].level = 7;
fleet.ships[0].level.forceLevel(2);
fleet.ships[1].level.forceLevel(4);
fleet.ships[2].level.forceLevel(7);
expect(fleet.getLevel()).toEqual(4);
});

View file

@ -71,10 +71,10 @@ module TS.SpaceTac {
var sum = 0;
this.ships.forEach((ship: Ship) => {
sum += ship.level;
sum += ship.level.get();
});
var avg = sum / this.ships.length;
return Math.round(avg);
return Math.floor(avg);
}
// Check if the fleet still has living ships

View file

@ -400,5 +400,28 @@ module TS.SpaceTac.Specs {
expect(ship.listEquipment()).toEqual([]);
expect(ship.cargo).toEqual([equipment]);
});
it("allow skills upgrading from current level", function () {
let ship = new Ship();
expect(ship.level.get()).toBe(1);
expect(ship.getAvailableUpgradePoints()).toBe(10);
ship.level.forceLevel(2);
expect(ship.level.get()).toBe(2);
expect(ship.getAvailableUpgradePoints()).toBe(15);
expect(ship.getAttribute("skill_energy")).toBe(0);
ship.upgradeSkill("skill_energy");
expect(ship.getAttribute("skill_energy")).toBe(1);
range(50).forEach(() => ship.upgradeSkill("skill_gravity"));
expect(ship.getAttribute("skill_energy")).toBe(1);
expect(ship.getAttribute("skill_gravity")).toBe(14);
expect(ship.getAvailableUpgradePoints()).toBe(0);
ship.updateAttributes();
expect(ship.getAttribute("skill_energy")).toBe(1);
expect(ship.getAttribute("skill_gravity")).toBe(14);
});
});
}

View file

@ -3,10 +3,23 @@
module TS.SpaceTac {
/**
* Set of upgradable skills for a ship
*/
export class ShipSkills {
// Skills
skill_material = new ShipAttribute("material skill")
skill_energy = new ShipAttribute("energy skill")
skill_electronics = new ShipAttribute("electronics skill")
skill_human = new ShipAttribute("human skill")
skill_time = new ShipAttribute("time skill")
skill_gravity = new ShipAttribute("gravity skill")
}
/**
* Set of ShipAttribute for a ship
*/
export class ShipAttributes {
export class ShipAttributes extends ShipSkills {
// Attribute controlling the play order
initiative = new ShipAttribute("initiative")
// Maximal hull value
@ -19,13 +32,6 @@ module TS.SpaceTac {
power_initial = new ShipAttribute("initial power")
// Power value recovered each turn
power_recovery = new ShipAttribute("power recovery")
// Skills
skill_material = new ShipAttribute("material skill")
skill_energy = new ShipAttribute("energy skill")
skill_electronics = new ShipAttribute("electronics skill")
skill_human = new ShipAttribute("human skill")
skill_time = new ShipAttribute("time skill")
skill_gravity = new ShipAttribute("gravity skill")
}
/**
@ -40,6 +46,7 @@ module TS.SpaceTac {
/**
* Static attributes and values object for name queries
*/
export const SHIP_SKILLS = new ShipSkills();
export const SHIP_ATTRIBUTES = new ShipAttributes();
export const SHIP_VALUES = new ShipValues();
@ -51,7 +58,8 @@ module TS.SpaceTac {
fleet: Fleet
// Level of this ship
level: number
level = new ShipLevel()
skills = new ShipSkills()
// Name of the ship
name: string
@ -91,13 +99,9 @@ module TS.SpaceTac {
// Priority in play_order
play_priority = 0;
// Upgrade points available
upgrade_points = 0;
// Create a new ship inside a fleet
constructor(fleet: Fleet | null = null, name = "Ship") {
this.fleet = fleet || new Fleet();
this.level = 1;
this.name = name;
this.model = "default";
this.alive = true;
@ -172,6 +176,24 @@ module TS.SpaceTac {
return actions;
}
/**
* Get the number of upgrade points available to improve skills
*/
getAvailableUpgradePoints(): number {
let used = keys(SHIP_SKILLS).map(skill => this.skills[skill].get()).reduce((a, b) => a + b, 0);
return this.level.getSkillPoints() - used;
}
/**
* Try to upgrade a skill by 1 point
*/
upgradeSkill(skill: keyof ShipSkills) {
if (this.getAvailableUpgradePoints() > 0) {
this.skills[skill].add(1);
this.updateAttributes();
}
}
// Add an event to the battle log, if any
addBattleEvent(event: BaseLogEvent): void {
var battle = this.getBattle();
@ -550,8 +572,16 @@ module TS.SpaceTac {
// Update attributes, taking into account attached equipment and active effects
updateAttributes(): void {
if (this.alive) {
// Sum all attribute effects
var new_attrs = new ShipAttributes();
// TODO better typing for iteritems
// Apply base skills
iteritems(<any>this.skills, (key, skill: ShipAttribute) => {
new_attrs[key].add(skill.get());
});
// Sum all attribute effects
this.collectEffects("attr").forEach((effect: AttributeEffect) => {
new_attrs[effect.attrcode].add(effect.value);
});
@ -561,7 +591,7 @@ module TS.SpaceTac {
new_attrs[effect.attrcode].setMaximal(effect.value);
});
// TODO better typing
// Set final attributes
iteritems(<any>new_attrs, (key, value) => {
this.setAttribute(<keyof ShipAttributes>key, (<ShipAttribute>value).get());
});

View file

@ -0,0 +1,43 @@
module TS.SpaceTac.Specs {
describe("ShipLevel", () => {
it("level up from experience points", () => {
let level = new ShipLevel();
expect(level.get()).toEqual(1);
expect(level.getNextGoal()).toEqual(100);
expect(level.getSkillPoints()).toEqual(10);
level.addExperience(60); // 60
expect(level.get()).toEqual(1);
expect(level.checkLevelUp()).toBe(false);
level.addExperience(70); // 130
expect(level.get()).toEqual(1);
expect(level.checkLevelUp()).toBe(true);
expect(level.get()).toEqual(2);
expect(level.getNextGoal()).toEqual(300);
expect(level.getSkillPoints()).toEqual(15);
level.addExperience(200); // 330
expect(level.get()).toEqual(2);
expect(level.checkLevelUp()).toBe(true);
expect(level.get()).toEqual(3);
expect(level.getNextGoal()).toEqual(600);
expect(level.getSkillPoints()).toEqual(20);
level.addExperience(320); // 650
expect(level.get()).toEqual(3);
expect(level.checkLevelUp()).toBe(true);
expect(level.get()).toEqual(4);
expect(level.getNextGoal()).toEqual(1000);
expect(level.getSkillPoints()).toEqual(25);
});
it("forces a given level", () => {
let level = new ShipLevel();
expect(level.get()).toEqual(1);
level.forceLevel(10);
expect(level.get()).toEqual(10);
});
});
}

61
src/core/ShipLevel.ts Normal file
View file

@ -0,0 +1,61 @@
module TS.SpaceTac {
/**
* Level and experience system for a ship.
*/
export class ShipLevel {
private level = 1;
private experience = 0;
/**
* Get current level
*/
get(): number {
return this.level;
}
/**
* Get the next experience goal to reach, to gain one level
*/
getNextGoal(): number {
return isum(imap(irange(this.level), i => (i + 1) * 100));
}
/**
* Force experience gain, to reach a given level
*/
forceLevel(level: number) {
while (this.level < level) {
this.addExperience(this.getNextGoal());
this.checkLevelUp();
}
}
/**
* Check for level-up
*
* Returns true if level changed
*/
checkLevelUp(): boolean {
let changed = false;
while (this.experience > this.getNextGoal()) {
this.level++;
changed = true;
}
return changed;
}
/**
* Add experience points
*/
addExperience(points: number) {
this.experience += points;
}
/**
* Get skill points given by current level
*/
getSkillPoints(): number {
return 5 + 5 * this.level;
}
}
}

View file

@ -93,6 +93,7 @@ module TS.SpaceTac.UI {
this.loadImage("character/close.png");
this.loadImage("character/ship.png");
this.loadImage("character/ship-selected.png");
this.loadImage("character/skill-upgrade.png");
this.loadImage("character/cargo-slot.png");
this.loadImage("character/equipment-slot.png");
this.loadImage("character/slot-power.png");

View file

@ -58,6 +58,10 @@ module TS.SpaceTac.UI {
this.updateAttributes();
this.updateEffects();
let level = new Phaser.Text(this.game, 103, 22, `${ship.level.get()}`, { align: "center", font: "bold 10pt Arial", fill: "#000000" });
level.anchor.set(0.5, 0.5);
this.addChild(level);
Tools.setHoverClick(this, () => list.battleview.cursorOnShip(ship), () => list.battleview.cursorOffShip(ship), () => list.battleview.cursorClicked());
}

View file

@ -27,8 +27,9 @@ module TS.SpaceTac.UI {
// Ship level
ship_level: Phaser.Text;
// Ship upgrade points
ship_upgrades: Phaser.Text;
// Ship skill upgrade
ship_upgrade_points: Phaser.Text;
ship_upgrades: Phaser.Group;
// Ship slots
ship_slots: Phaser.Group;
@ -75,8 +76,11 @@ module TS.SpaceTac.UI {
this.ship_level.anchor.set(0.5, 0.5);
this.addChild(this.ship_level);
this.ship_upgrades = new Phaser.Text(this.game, 1066, 1054, "", { align: "center", font: "30pt Arial", fill: "#FFFFFF" });
this.ship_upgrades.anchor.set(0.5, 0.5);
this.ship_upgrade_points = new Phaser.Text(this.game, 1066, 1054, "", { align: "center", font: "30pt Arial", fill: "#FFFFFF" });
this.ship_upgrade_points.anchor.set(0.5, 0.5);
this.addChild(this.ship_upgrade_points);
this.ship_upgrades = new Phaser.Group(this.game);
this.addChild(this.ship_upgrades);
this.ship_slots = new Phaser.Group(this.game);
@ -106,29 +110,40 @@ module TS.SpaceTac.UI {
let x1 = 664;
let x2 = 1066;
let y = 662;
this.addAttribute(SHIP_ATTRIBUTES.initiative, x1, y);
this.addAttribute(SHIP_ATTRIBUTES.hull_capacity, x1, y + 64);
this.addAttribute(SHIP_ATTRIBUTES.shield_capacity, x1, y + 128);
this.addAttribute(SHIP_ATTRIBUTES.power_capacity, x1, y + 192);
this.addAttribute(SHIP_ATTRIBUTES.power_initial, x1, y + 256);
this.addAttribute(SHIP_ATTRIBUTES.power_recovery, x1, y + 320);
this.addAttribute(SHIP_ATTRIBUTES.skill_material, x2, y);
this.addAttribute(SHIP_ATTRIBUTES.skill_electronics, x2, y + 64);
this.addAttribute(SHIP_ATTRIBUTES.skill_energy, x2, y + 128);
this.addAttribute(SHIP_ATTRIBUTES.skill_human, x2, y + 192);
this.addAttribute(SHIP_ATTRIBUTES.skill_gravity, x2, y + 256);
this.addAttribute(SHIP_ATTRIBUTES.skill_time, x2, y + 320);
this.addAttribute("initiative", x1, y);
this.addAttribute("hull_capacity", x1, y + 64);
this.addAttribute("shield_capacity", x1, y + 128);
this.addAttribute("power_capacity", x1, y + 192);
this.addAttribute("power_initial", x1, y + 256);
this.addAttribute("power_recovery", x1, y + 320);
this.addAttribute("skill_material", x2, y);
this.addAttribute("skill_electronics", x2, y + 64);
this.addAttribute("skill_energy", x2, y + 128);
this.addAttribute("skill_human", x2, y + 192);
this.addAttribute("skill_gravity", x2, y + 256);
this.addAttribute("skill_time", x2, y + 320);
}
/**
* Add an attribute display
*/
private addAttribute(attribute: ShipAttribute, x: number, y: number) {
private addAttribute(attribute: keyof ShipAttributes, x: number, y: number) {
let text = new Phaser.Text(this.game, x, y, "", { align: "center", font: "18pt Arial", fill: "#FFFFFF" });
text.anchor.set(0.5, 0.5);
this.addChild(text);
this.attributes[attribute.name] = text;
this.attributes[SHIP_ATTRIBUTES[attribute].name] = text;
if (SHIP_SKILLS[attribute]) {
let button = new Phaser.Button(this.game, x + 54, y - 4, "character-skill-upgrade", () => {
this.ship.upgradeSkill(<keyof ShipSkills>attribute);
this.refresh();
});
button.anchor.set(0.5, 0.5);
this.ship_upgrades.add(button);
this.view.tooltip.bindStaticText(button, `Spend one point to upgrade ${SHIP_ATTRIBUTES[attribute].name}`);
}
}
/**
@ -172,9 +187,12 @@ module TS.SpaceTac.UI {
this.equipments.removeAll(true);
let upgrade_points = ship.getAvailableUpgradePoints();
this.ship_name.setText(ship.name);
this.ship_level.setText(ship.level.toString());
this.ship_upgrades.setText(ship.upgrade_points.toString());
this.ship_level.setText(ship.level.get().toString());
this.ship_upgrade_points.setText(upgrade_points.toString());
this.ship_upgrades.visible = upgrade_points > 0;
iteritems(<any>ship.attributes, (key, value: ShipAttribute) => {
let text = this.attributes[value.name];