1
0
Fork 0

character sheet: Added equipment description

This commit is contained in:
Michaël Lemaire 2017-04-19 00:55:59 +02:00
parent 088ed18391
commit d5b37ff850
34 changed files with 179 additions and 79 deletions

View file

@ -23,8 +23,8 @@ After making changes to sources, you need to recompile:
## Credits
* **[Lemaire Michael](https://thunderk.net/)** - Code and graphics
* **Forgo Nicolas** - Ship models
* **[Michaël Lemaire](https://thunderk.net/)** - Code and graphics
* **Nicolas Forgo** - Ship models
* **[Phaser](http://phaser.io)** - Game engine
* **[Kevin MacLeod](http://www.incompetech.com/)** - Musics

1
TODO
View file

@ -1,5 +1,4 @@
* UI: Use a common component class, and a layer abstraction
* Character sheet: add tooltips (on values, slots and equipments)
* Character sheet: add initial character creation
* Character sheet: disable interaction during battle (except for loot screen)
* Character sheet: paginate loot and shop items

View file

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View file

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View file

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -77,6 +77,7 @@ module TS.SpaceTac {
expect(battle.playing_ship_index).toBeNull();
// Force play order
iforeach(battle.iships(), ship => ship.setAttribute("initiative", 1));
var gen = new SkewedRandomGenerator([0.1, 0.2, 0.0]);
battle.throwInitiative(gen);

View file

@ -1,5 +1,16 @@
module TS.SpaceTac.Specs {
describe("Equipment", () => {
it("generates a full name", function () {
let equipment = new Equipment(SlotType.Weapon, "rayofdeath");
expect(equipment.getFullName()).toEqual("Level 1 rayofdeath");
equipment.name = "Ray of Death";
expect(equipment.getFullName()).toEqual("Level 1 Ray of Death");
equipment.quality = EquipmentQuality.LEGENDARY;
expect(equipment.getFullName()).toEqual("Level 1 Legendary Ray of Death");
});
it("checks capabilities requirements", function () {
var equipment = new Equipment();
var ship = new Ship();
@ -34,20 +45,20 @@ module TS.SpaceTac.Specs {
it("generates a description of the effects", function () {
let equipment = new Equipment();
expect(equipment.getActionDescription()).toEqual("does nothing");
expect(equipment.getEffectsDescription()).toEqual("does nothing");
let action = new FireWeaponAction(equipment, 1, 200, 0, [
new DamageEffect(50)
]);
equipment.action = action;
expect(equipment.getActionDescription()).toEqual("- Fire: 50 damage on target");
expect(equipment.getEffectsDescription()).toEqual("Fire (power usage 1, max range 200km):\n- do 50 damage on target");
action.blast = 20;
expect(equipment.getActionDescription()).toEqual("- Fire: 50 damage in 20km radius");
expect(equipment.getEffectsDescription()).toEqual("Fire (power usage 1, max range 200km):\n- do 50 damage in 20km radius");
action.blast = 0;
action.effects.push(new StickyEffect(new AttributeLimitEffect("shield_capacity", 200), 3));
expect(equipment.getActionDescription()).toEqual("- Fire: 50 damage on target\n- Fire: limit shield capacity to 200 for 3 turns on target");
expect(equipment.getEffectsDescription()).toEqual("Fire (power usage 1, max range 200km):\n- do 50 damage on target\n- limit shield capacity to 200 for 3 turns on target");
});
it("gets a minimal level, based on skills requirements", function () {

View file

@ -1,35 +1,56 @@
module TS.SpaceTac {
/**
* Quality of loot.
*/
export enum EquipmentQuality {
WEAK,
COMMON,
FINE,
PREMIUM,
LEGENDARY
}
// Piece of equipment to attach in slots
export class Equipment {
// Type of slot this equipment can fit in
slot_type: SlotType | null;
slot_type: SlotType | null
// Actual slot this equipment is attached to
attached_to: Slot | null = null;
attached_to: Slot | null = null
// Identifiable equipment code (may be used by UI to customize visual effects)
code: string;
code: string
// Equipment name
name: string;
name: string
// Equipment generic description
description: string
// Indicative equipment level
level = 1
// Indicative equipment quality
quality = EquipmentQuality.COMMON
// Minimum skills to be able to equip this
requirements: { [key: string]: number };
requirements: { [key: string]: number }
// Permanent effects on the ship that equips this
effects: BaseEffect[];
effects: BaseEffect[]
// Action available when equipped
action: BaseAction;
action: BaseAction
// Usage made of this equipment (will lower the sell price)
usage: number;
usage: number
// Basic constructor
constructor(slot: SlotType | null = null, code = "equipment") {
this.slot_type = slot;
this.code = code;
this.name = code;
this.description = "";
this.requirements = {};
this.effects = [];
this.action = new BaseAction("nothing", "Do nothing", false);
@ -39,6 +60,28 @@ module TS.SpaceTac {
return this.attached_to ? `${this.attached_to.ship.name} - ${this.name}` : this.name;
}
/**
* Get the fully qualified name (e.g. "Level 4 Strong Ray of Death")
*/
getFullName(): string {
let name = this.name;
if (this.quality != EquipmentQuality.COMMON) {
name = capitalize(EquipmentQuality[this.quality].toLowerCase()) + " " + name;
}
return `Level ${this.level} ${name}`;
}
/**
* Get the full textual description for this equipment (without the full name).
*/
getFullDescription(): string {
let description = this.getEffectsDescription();
if (this.description) {
description += "\n\n" + this.description;
}
return description;
}
/**
* Get the minimum level at which the requirements in skill may be fulfilled.
*
@ -83,18 +126,19 @@ module TS.SpaceTac {
/**
* Get a human readable description of the effects of this equipment
*/
getActionDescription(): string {
getEffectsDescription(): string {
let parts: string[] = [];
this.effects.forEach(effect => {
parts.push(`- Equip: ${effect.getDescription()}`);
});
if (this.effects.length > 0) {
parts.push(["When equipped:"].concat(this.effects.map(effect => "- " + effect.getDescription())).join("\n"));
}
this.action.getEffectsDescription().forEach(desc => {
parts.push(`- ${this.action.name}: ${desc}`);
});
let action_desc = this.action.getEffectsDescription();
if (action_desc != "") {
parts.push(action_desc);
}
return parts.length > 0 ? parts.join("\n") : "does nothing";
return parts.length > 0 ? parts.join("\n\n") : "does nothing";
}
}
}

View file

@ -1,14 +1,4 @@
module TS.SpaceTac {
/**
* Quality of loot.
*/
export enum LootQuality {
WEAK,
COMMON,
FINE,
PREMIUM
}
/**
* A leveled value is either a number multiplied by the level, or a custom iterator
*/
@ -90,29 +80,34 @@ module TS.SpaceTac {
*/
export class LootTemplate {
// Type of slot this equipment will fit in
slot: SlotType;
slot: SlotType
// Base name that will be given to generated equipment
name: string;
name: string
// Generic description of the equipment
description: string
// Modifiers applied to obtain the "common" equipment, based on level
protected base_modifiers: ((equipment: Equipment, level: number) => void)[];
constructor(slot: SlotType, name: string) {
constructor(slot: SlotType, name: string, description = "") {
this.slot = slot;
this.name = name;
this.description = description;
this.base_modifiers = [];
}
/**
* Generate a new equipment of a given level and quality
*/
generate(level: number, quality: LootQuality = LootQuality.COMMON, random = RandomGenerator.global): Equipment {
generate(level: number, quality = EquipmentQuality.COMMON, random = RandomGenerator.global): Equipment {
let result = new Equipment();
result.slot_type = this.slot;
result.code = (this.name || "").toLowerCase().replace(/ /g, "");
result.name = this.name;
result.description = this.description;
this.base_modifiers.forEach(modifier => modifier(result, level));

View file

@ -108,8 +108,6 @@ module TS.SpaceTac {
this.sticky_effects = [];
this.slots = [];
this.attributes.initiative.set(1); // TODO Should not be needed
this.arena_x = 0;
this.arena_y = 0;
this.arena_angle = 0;

View file

@ -37,7 +37,7 @@ module TS.SpaceTac {
// Add an engine, allowing a ship to move *distance*, for each action points
static addEngine(ship: Ship, distance: number): Equipment {
var equipment = this.getOrGenEquipment(ship, SlotType.Engine, new Equipments.ConventionalEngine(), true);
var equipment = this.getOrGenEquipment(ship, SlotType.Engine, new Equipments.RocketEngine(), true);
(<MoveAction>equipment.action).distance_per_power = distance;
return equipment;
}
@ -53,7 +53,7 @@ module TS.SpaceTac {
// Set a ship action points, adding/updating an equipment if needed
static setShipAP(ship: Ship, points: number, recovery: number = 0): void {
var equipment = this.getOrGenEquipment(ship, SlotType.Power, new Equipments.BasicPowerCore());
var equipment = this.getOrGenEquipment(ship, SlotType.Power, new Equipments.NuclearReactor());
equipment.effects.forEach(effect => {
if (effect instanceof AttributeEffect) {
@ -74,7 +74,7 @@ module TS.SpaceTac {
// Set a ship hull and shield points, adding/updating an equipment if needed
static setShipHP(ship: Ship, hull_points: number, shield_points: number): void {
var armor = TestTools.getOrGenEquipment(ship, SlotType.Hull, new Equipments.IronHull());
var shield = TestTools.getOrGenEquipment(ship, SlotType.Shield, new Equipments.BasicForceField());
var shield = TestTools.getOrGenEquipment(ship, SlotType.Shield, new Equipments.ForceField());
armor.effects.forEach(effect => {
if (effect instanceof AttributeEffect) {

View file

@ -119,10 +119,10 @@ module TS.SpaceTac {
}
/**
* Get description of effects (one line per effect)
* Get textual description of effects
*/
getEffectsDescription(): string[] {
return [];
getEffectsDescription(): string {
return "";
}
}
}

View file

@ -65,9 +65,16 @@ module TS.SpaceTac {
}
}
getEffectsDescription(): string[] {
let desc = `drone for ${this.lifetime} turn${this.lifetime > 1 ? "s" : ""}, in ${this.deploy_distance}km range, with ${this.effect_radius}km radius effects`;
return [desc].concat(this.effects.map(effect => effect.getDescription()));
getEffectsDescription(): string {
let desc = `Deploy drone for ${this.lifetime} turn${this.lifetime > 1 ? "s" : ""} (power usage ${this.power}, max range ${this.deploy_distance}km)`;
let effects = this.effects.map(effect => {
let suffix = `for ships in ${this.effect_radius}km radius`;
if (effect instanceof StickyEffect) {
suffix = `for ${effect.duration} turn${effect.duration > 1 ? "s" : ""} ${suffix}`;
}
return "- " + effect.getDescription() + " " + suffix;
});
return `${desc}:\n${effects.join("\n")}`;
}
}
}

View file

@ -92,14 +92,20 @@ module TS.SpaceTac {
effects.forEach(([ship, effect]) => effect.applyOnShip(ship));
}
getEffectsDescription(): string[] {
return this.effects.map(effect => {
getEffectsDescription(): string {
if (this.effects.length == 0) {
return "";
}
let desc = `${this.name} (power usage ${this.power}, max range ${this.range}km)`;
let effects = this.effects.map(effect => {
let suffix = this.blast ? `in ${this.blast}km radius` : "on target";
if (effect instanceof StickyEffect) {
suffix = `for ${effect.duration} turn${effect.duration > 1 ? "s" : ""} ${suffix}`;
}
return effect.getDescription() + " " + suffix;
return "- " + effect.getDescription() + " " + suffix;
});
return `${desc}:\n${effects.join("\n")}`;
}
}
}

View file

@ -76,5 +76,9 @@ module TS.SpaceTac {
protected customApply(ship: Ship, target: Target) {
ship.moveTo(target.x, target.y);
}
getEffectsDescription(): string {
return `Move: ${this.distance_per_power}km per power point`;
}
}
}

View file

@ -5,6 +5,7 @@ module TS.SpaceTac.Specs {
battle.fleets[0].addShip(new Ship(null, "0-0"));
battle.fleets[1].addShip(new Ship(null, "1-0"));
battle.fleets[1].addShip(new Ship(null, "1-1"));
iforeach(battle.iships(), ship => ship.setAttribute("initiative", 1));
var random = new SkewedRandomGenerator([0, 0.5, 1]);
battle.throwInitiative(random);

View file

@ -0,0 +1,24 @@
module TS.SpaceTac {
describe("AttributeEffect", function () {
it("is not applied directly", function () {
let ship = new Ship();
expect(ship.getAttribute("initiative")).toBe(0);
let effect = new AttributeEffect("initiative", 20);
effect.applyOnShip(ship);
expect(ship.getAttribute("initiative")).toBe(0);
ship.sticky_effects.push(new StickyEffect(effect, 2));
ship.updateAttributes();
expect(ship.getAttribute("initiative")).toBe(20);
});
it("has a description", function () {
let effect = new AttributeEffect("initiative", 12);
expect(effect.getDescription()).toEqual("initiative +12");
effect = new AttributeEffect("shield_capacity", -4);
expect(effect.getDescription()).toEqual("shield capacity -4");
});
});
}

View file

@ -32,5 +32,10 @@ module TS.SpaceTac {
getFullCode(): string {
return this.code + "-" + this.attrcode;
}
getDescription(): string {
let attrname = SHIP_ATTRIBUTES[this.attrcode].name;
return `${attrname} ${this.value > 0 ? "+" : "-"}${Math.abs(this.value)}`;
}
}
}

View file

@ -50,7 +50,7 @@ module TS.SpaceTac {
}
getDescription(): string {
return `${this.value} damage`;
return `do ${this.value} damage`;
}
}
}

View file

@ -1,7 +1,7 @@
module TS.SpaceTac.Equipments {
describe("BasicForceField", function () {
describe("ForceField", function () {
it("generates equipment based on level", function () {
let template = new BasicForceField();
let template = new ForceField();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_energy": 1 });

View file

@ -1,9 +1,9 @@
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
export class BasicForceField extends LootTemplate {
export class ForceField extends LootTemplate {
constructor() {
super(SlotType.Shield, "Basic Force Field");
super(SlotType.Shield, "Force Field", "A basic force field, generated by radiating waves of compressed energy");
this.setSkillsRequirements({ "skill_energy": 1 });
this.addAttributeEffect("shield_capacity", istep(100, irepeat(50)));

View file

@ -3,7 +3,7 @@
module TS.SpaceTac.Equipments {
export class GatlingGun extends LootTemplate {
constructor() {
super(SlotType.Weapon, "Gatling Gun");
super(SlotType.Weapon, "Gatling Gun", "Mechanical weapon using loads of metal bullets propelled by guided explosions");
this.setSkillsRequirements({ "skill_material": 1 });
this.addFireAction(irepeat(3), irepeat(600), 0, [

View file

@ -3,7 +3,7 @@
module TS.SpaceTac.Equipments {
export class IronHull extends LootTemplate {
constructor() {
super(SlotType.Hull, "Iron Hull");
super(SlotType.Hull, "Iron Hull", "Protective hull, based on layered iron alloys");
this.setSkillsRequirements({ "skill_material": 1 });
this.addAttributeEffect("hull_capacity", istep(200, irepeat(50)));

View file

@ -1,7 +1,7 @@
module TS.SpaceTac.Equipments {
describe("BasicPowerCore", function () {
describe("NuclearReactor", function () {
it("generates equipment based on level", function () {
let template = new BasicPowerCore();
let template = new NuclearReactor();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_energy": 1 });

View file

@ -1,9 +1,9 @@
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
export class BasicPowerCore extends LootTemplate {
export class NuclearReactor extends LootTemplate {
constructor() {
super(SlotType.Power, "Basic Power Core");
super(SlotType.Power, "Nuclear Reactor", "A standard nuclear power core, drawing power from atom fusion cycles");
this.setSkillsRequirements({ "skill_energy": 1 });
this.addAttributeEffect("initiative", istep(1));

View file

@ -3,7 +3,7 @@
module TS.SpaceTac.Equipments {
export class PowerDepleter extends LootTemplate {
constructor() {
super(SlotType.Weapon, "Power Depleter");
super(SlotType.Weapon, "Power Depleter", "Direct-hit weapon that creates an energy well near the target, sucking its power surplus");
this.setSkillsRequirements({ "skill_energy": 1 });
this.addFireAction(irepeat(4), istep(500, irepeat(20)), 0, [

View file

@ -6,7 +6,7 @@ module TS.SpaceTac.Equipments {
*/
export class RepairDrone extends LootTemplate {
constructor() {
super(SlotType.Weapon, "Repair Drone");
super(SlotType.Weapon, "Repair Drone", "Drone able to repair small hull breaches, remotely controlled by human pilots");
this.setSkillsRequirements({ "skill_human": 1 });
this.addDroneAction(irepeat(4), istep(300, irepeat(10)), istep(1, irepeat(0.2)), istep(100, irepeat(10)), [

View file

@ -1,7 +1,7 @@
module TS.SpaceTac.Equipments {
describe("ConventionalEngine", function () {
describe("Rocket Engine", function () {
it("generates equipment based on level", function () {
let template = new ConventionalEngine();
let template = new RocketEngine();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_energy": 1 });

View file

@ -1,10 +1,9 @@
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
// Equipment: Conventional Engine
export class ConventionalEngine extends LootTemplate {
export class RocketEngine extends LootTemplate {
constructor() {
super(SlotType.Engine, "Conventional Engine");
super(SlotType.Engine, "Rocket Engine", "First-era conventional deep-space engine, based on gas exhausts pushed through a nozzle");
this.setSkillsRequirements({ "skill_energy": 1 });
this.addAttributeEffect("initiative", 1);

View file

@ -3,7 +3,7 @@
module TS.SpaceTac.Equipments {
export class SubMunitionMissile extends LootTemplate {
constructor() {
super(SlotType.Weapon, "SubMunition Missile");
super(SlotType.Weapon, "SubMunition Missile", "Explosive missile releasing small shelled payloads, that will in turn explode on impact");
this.setSkillsRequirements({ "skill_material": 1 });
this.addFireAction(irepeat(4), istep(500, irepeat(20)), istep(150, irepeat(5)), [

View file

@ -106,9 +106,9 @@ module TS.SpaceTac.UI {
this.loadImage("character/price-tag.png");
this.loadImage("character/scroll.png");
this.loadImage("equipment/ironhull.png");
this.loadImage("equipment/basicforcefield.png");
this.loadImage("equipment/basicpowercore.png");
this.loadImage("equipment/conventionalengine.png");
this.loadImage("equipment/forcefield.png");
this.loadImage("equipment/nuclearreactor.png");
this.loadImage("equipment/rocketengine.png");
// Load ships
this.loadShip("scout");

View file

@ -58,7 +58,7 @@ module TS.SpaceTac.UI {
let cost = action.action.getActionPointsUsage(action.ship, null);
this.cost.setText(cost == 0 ? "" : `Cost: ${cost} power`);
}
this.description.setText(action.action.equipment ? action.action.equipment.getActionDescription() : "");
this.description.setText(action.action.equipment ? action.action.equipment.getEffectsDescription() : "");
let position = this.bar.action_icons.indexOf(action);
if (action.action instanceof EndTurnAction) {

View file

@ -32,7 +32,6 @@ module TS.SpaceTac.UI {
sheet: CharacterSheet
item: Equipment
container: CharacterEquipmentContainer
tooltip: string
price: number
constructor(sheet: CharacterSheet, equipment: Equipment, container: CharacterEquipmentContainer) {
@ -42,7 +41,6 @@ module TS.SpaceTac.UI {
this.sheet = sheet;
this.item = equipment;
this.container = container;
this.tooltip = equipment.name;
this.price = 0;
this.container.addEquipment(this, null, false);
@ -52,8 +50,7 @@ module TS.SpaceTac.UI {
this.setupDragDrop(sheet);
this.snapToContainer();
// TODO better tooltip (with equipment characteristics)
sheet.view.tooltip.bindDynamicText(this, () => this.tooltip);
sheet.view.tooltip.bind(this, container => this.fillTooltip(container));
}
/**
@ -143,5 +140,14 @@ module TS.SpaceTac.UI {
}
}
}
/**
* Fill a tooltip with equipment data
*/
fillTooltip(container: Phaser.Group): boolean {
container.add(new Phaser.Text(container.game, 0, 0, this.item.getFullName(), { font: "bold 20pt Arial", fill: "#cccccc" }));
container.add(new Phaser.Text(container.game, 0, 40, this.item.getFullDescription(), { font: "18pt Arial", fill: "#cccccc" }));
return true;
}
}
}

View file

@ -30,7 +30,7 @@ module TS.SpaceTac.UI {
let ttbounds = this.container.getBounds();
let background = new Phaser.Graphics(this.container.game, 0, 0);
this.container.add(background);
background.beginFill(0x202225, 0.8);
background.beginFill(0x202225, 0.9);
background.drawRect(-10, -10, ttbounds.width + 20, ttbounds.height + 20);
background.endFill();
this.container.sendToBack(background);