Added skill upgrading
13
README.md
|
@ -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
|
@ -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)
|
||||
|
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
out/assets/images/character/skill-upgrade.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
|
@ -1 +1 @@
|
|||
Subproject commit fb3268b8ef0c8f6e32c5cf10e896b9a134d45f6a
|
||||
Subproject commit 2b11ca44c3dcf368baa9099467842750aa176be7
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
|
|
43
src/core/ShipLevel.spec.ts
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|