1
0
Fork 0

Refactoring of attributes/values system

This commit is contained in:
Michaël Lemaire 2017-02-07 20:15:21 +01:00
parent fcb45346e4
commit 8e43116a1f
56 changed files with 657 additions and 769 deletions

5
TODO
View file

@ -1,8 +1,7 @@
* Restore serialization
* Refactor attribute effects (add/sub, value/max, temporary/permanent)
* Drones: add hooks on events
* Drones: add hooks on game events
* Drones: add sprite, radius and tooltip
* Repair drone: add graphics
* Repair drone: add graphics and proper description
* Allow to cancel last moves
* Effect should be random in a range (eg. "damage target 50-75")
* Add an overload/cooling system

View file

@ -1,99 +0,0 @@
module TS.SpaceTac.Game {
describe("Attribute", function () {
it("is initially not limited", function () {
var attr = new Attribute();
attr.set(8888888);
expect(attr.current).toBe(8888888);
});
it("enumerates codes", function () {
var result = [];
Attribute.forEachCode((code: AttributeCode) => {
result.push(code);
});
expect(result).toEqual([
AttributeCode.Initiative,
AttributeCode.Hull,
AttributeCode.Shield,
AttributeCode.Power,
AttributeCode.Power_Recovery,
AttributeCode.Power_Initial,
AttributeCode.Cap_Material,
AttributeCode.Cap_Energy,
AttributeCode.Cap_Electronics,
AttributeCode.Cap_Human,
AttributeCode.Cap_Time,
AttributeCode.Cap_Gravity,
AttributeCode.Misc
]);
});
it("gets human readable name", function () {
expect(ATTRIBUTE_NAMES[AttributeCode.Initiative]).toEqual("initiative");
expect(ATTRIBUTE_NAMES[AttributeCode.Power]).toEqual("power");
});
it("applies minimal and maximal value", function () {
var attr = new Attribute(AttributeCode.Misc, 50, 100);
expect(attr.current).toBe(50);
attr.add(8);
expect(attr.current).toBe(58);
attr.add(60);
expect(attr.current).toBe(100);
attr.add(-72);
expect(attr.current).toBe(28);
attr.add(-60);
expect(attr.current).toBe(0);
attr.set(8);
expect(attr.current).toBe(8);
attr.set(-4);
expect(attr.current).toBe(0);
attr.set(105);
expect(attr.current).toBe(100);
attr.setMaximal(50);
expect(attr.current).toBe(50);
attr.setMaximal(80);
expect(attr.current).toBe(50);
});
it("tells if value changed", function () {
var result: boolean;
var attr = new Attribute(AttributeCode.Misc, 50, 100);
expect(attr.current).toBe(50);
result = attr.set(51);
expect(result).toBe(true);
result = attr.set(51);
expect(result).toBe(false);
result = attr.add(1);
expect(result).toBe(true);
result = attr.add(0);
expect(result).toBe(false);
result = attr.add(1000);
expect(result).toBe(true);
result = attr.add(2000);
expect(result).toBe(false);
result = attr.set(-500);
expect(result).toBe(true);
result = attr.add(-600);
expect(result).toBe(false);
});
});
}

View file

@ -1,124 +0,0 @@
module TS.SpaceTac.Game {
// Code to identify
export enum AttributeCode {
// Initiative level
Initiative,
// Hull points (similar to health points or HP in other games)
Hull,
// Damage the shield can take
Shield,
// Power available to make actions (similar to action points or AP in other games)
Power,
// Power recovered each turn
Power_Recovery,
// Starting power in a battle
Power_Initial,
// Capability level in materials
Cap_Material,
// Capability level in energy
Cap_Energy,
// Capability level in electronics
Cap_Electronics,
// Capability level in human things
Cap_Human,
// Capability level in time manipulation
Cap_Time,
// Capability level in gravity manipulation
Cap_Gravity,
// Miscellaneous attribute
Misc
}
// Name mapping for attributes
export const ATTRIBUTE_NAMES = [
"initiative",
"hull",
"shield",
"power",
"power recovery",
"initial power",
"materials",
"energy",
"electronics",
"human",
"time",
"gravity"
]
// Value computed from equipment
// This value can be altered by effects
// Example attributes are health points, power recovery...
export class Attribute {
// Identifying code of this attribute
code: AttributeCode;
// Maximal attribute value
maximal: number;
// Current attribute value
current: number;
// Create an attribute
constructor(code: AttributeCode = AttributeCode.Misc, current: number = 0, maximal: number = null) {
this.code = code;
this.maximal = maximal;
this.current = current;
}
// Iterator over each code
static forEachCode(callback: (code: AttributeCode) => void) {
for (var val in AttributeCode) {
var parsed = parseInt(val, 10);
if (!isNaN(parsed)) {
callback(<AttributeCode>parsed);
}
}
}
// Set the maximal value
setMaximal(value: number): void {
this.maximal = value;
this.fix();
}
// Set an absolute value
// Returns true if the value changed
set(value: number): boolean {
var old_value = this.current;
this.current = value;
this.fix();
return this.current !== old_value;
}
// Add an offset to current value
// Returns true if the value changed
add(value: number): boolean {
var old_value = this.current;
this.current += value;
this.fix();
return this.current !== old_value;
}
// Fix the value to be integer, positive and lower than maximal
private fix(): void {
if (this.maximal !== null && this.current > this.maximal) {
this.current = this.maximal;
}
if (this.current < 0) {
this.current = 0;
}
}
}
}

View file

@ -1,25 +0,0 @@
module TS.SpaceTac.Game {
describe("AttributeCollection", function () {
it("sets and gets an attribute value", function () {
var coll = new AttributeCollection();
coll.setValue(AttributeCode.Initiative, 5);
expect(coll.getValue(AttributeCode.Initiative)).toBe(5);
expect(coll.getValue(AttributeCode.Hull)).toBe(0);
coll.setValue(AttributeCode.Hull, 2);
expect(coll.getValue(AttributeCode.Hull)).toBe(2);
});
it("sets and gets an attribute maximal", function () {
var coll = new AttributeCollection();
coll.setMaximum(AttributeCode.Initiative, 5);
expect(coll.getMaximum(AttributeCode.Initiative)).toBe(5);
expect(coll.getMaximum(AttributeCode.Hull)).toBe(null);
coll.setMaximum(AttributeCode.Hull, 2);
expect(coll.getMaximum(AttributeCode.Hull)).toBe(2);
});
});
}

View file

@ -1,55 +0,0 @@
module TS.SpaceTac.Game {
// Collection of several attributes
export class AttributeCollection {
// Attributes
private attributes: Attribute[];
// Base constructor
constructor() {
this.attributes = [];
}
// Get or create an attribute by its code
getRawAttr(code: AttributeCode): Attribute {
var attr = this.attributes[code];
if (!attr) {
attr = new Attribute(code);
this.attributes[code] = attr;
}
return attr;
}
// Get an attribute value
getValue(attrcode: AttributeCode): number {
var attr = this.getRawAttr(attrcode);
return attr.current;
}
// Set an attribute value
setValue(attrcode: AttributeCode, value: number): number {
var attr = this.getRawAttr(attrcode);
attr.set(value);
return attr.current;
}
// Add an offset to an attribute value
addValue(attrcode: AttributeCode, offset: number): number {
var attr = this.getRawAttr(attrcode);
attr.add(offset);
return attr.current;
}
// Get an attribute maximum
getMaximum(attrcode: AttributeCode): number {
var attr = this.getRawAttr(attrcode);
return attr.maximal;
}
// Set an attribute maximum
setMaximum(attrcode: AttributeCode, value: number): number {
var attr = this.getRawAttr(attrcode);
attr.setMaximal(value);
return attr.maximal;
}
}
}

View file

@ -5,15 +5,15 @@ module TS.SpaceTac.Game {
var fleet2 = new Fleet(null);
var ship1 = new Ship(fleet1, "F1S1");
ship1.initiative.setMaximal(2);
ship1.setAttribute("initiative", 2);
var ship2 = new Ship(fleet1, "F1S2");
ship2.initiative.setMaximal(4);
ship2.setAttribute("initiative", 4);
var ship3 = new Ship(fleet1, "F1S3");
ship3.initiative.setMaximal(1);
ship3.setAttribute("initiative", 1);
var ship4 = new Ship(fleet2, "F2S1");
ship4.initiative.setMaximal(8);
ship4.setAttribute("initiative", 8);
var ship5 = new Ship(fleet2, "F2S2");
ship5.initiative.setMaximal(2);
ship5.setAttribute("initiative", 2);
var battle = new Battle(fleet1, fleet2);
expect(battle.play_order.length).toBe(0);

View file

@ -84,7 +84,7 @@ module TS.SpaceTac.Game {
// Sort by throw result
play_order.sort(function (ship1: Ship, ship2: Ship) {
return (ship2.initiative.current - ship1.initiative.current);
return (ship2.play_priority - ship1.play_priority);
});
this.play_order = play_order;
}

View file

@ -59,7 +59,7 @@ module TS.SpaceTac.Game {
it("logs ship change events", function () {
var battle = Battle.newQuickRandom();
battle.log.clear();
battle.log.addFilter("attr");
battle.log.addFilter("value");
expect(battle.log.events.length).toBe(0);
battle.advanceToNextShip();
@ -70,7 +70,7 @@ module TS.SpaceTac.Game {
it("can receive simulated initial state events", function () {
var battle = Battle.newQuickRandom();
battle.log.clear();
battle.log.addFilter("attr");
battle.log.addFilter("value");
expect(battle.log.events.length).toBe(0);
battle.injectInitialEvents();

View file

@ -1,21 +1,21 @@
module TS.SpaceTac.Game.Specs {
describe("EffectTemplate", () => {
it("interpolates between weak and strong effects", () => {
var base_effect = new AttributeMaxEffect(AttributeCode.Hull, 6);
var base_effect = new AttributeEffect("hull_capacity", 6);
var template = new EffectTemplate(base_effect);
template.addModifier("value", new Range(2, 8));
var effect = <AttributeMaxEffect>template.generateFixed(0.0);
expect(effect.code).toEqual("attrmax");
var effect = <AttributeEffect>template.generateFixed(0.0);
expect(effect.code).toEqual("attr");
expect(effect.value).toEqual(2);
effect = <AttributeMaxEffect>template.generateFixed(1.0);
expect(effect.code).toEqual("attrmax");
effect = <AttributeEffect>template.generateFixed(1.0);
expect(effect.code).toEqual("attr");
expect(effect.value).toEqual(8);
effect = <AttributeMaxEffect>template.generateFixed(0.5);
expect(effect.code).toEqual("attrmax");
effect = <AttributeEffect>template.generateFixed(0.5);
expect(effect.code).toEqual("attr");
expect(effect.value).toEqual(5);
});
});

View file

@ -6,28 +6,28 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.canBeEquipped(ship)).toBe(true);
equipment.requirements.push(new Attribute(AttributeCode.Cap_Time, 2));
equipment.requirements["skill_time"] = 2;
expect(equipment.canBeEquipped(ship)).toBe(false);
ship.cap_time.set(1);
ship.attributes.skill_time.set(1);
expect(equipment.canBeEquipped(ship)).toBe(false);
ship.cap_time.set(2);
ship.attributes.skill_time.set(2);
expect(equipment.canBeEquipped(ship)).toBe(true);
ship.cap_time.set(3);
ship.attributes.skill_time.set(3);
expect(equipment.canBeEquipped(ship)).toBe(true);
// Second requirement
equipment.requirements.push(new Attribute(AttributeCode.Cap_Material, 3));
equipment.requirements["skill_material"] = 3;
expect(equipment.canBeEquipped(ship)).toBe(false);
ship.cap_material.set(4);
ship.attributes.skill_material.set(4);
expect(equipment.canBeEquipped(ship)).toBe(true);
});
@ -44,8 +44,8 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.getActionDescription()).toEqual("- 50 damage on all ships in 20km of impact");
equipment.blast = 0;
equipment.target_effects.push(new StickyEffect(new AttributeLimitEffect(AttributeCode.Shield, 200), 3));
expect(equipment.getActionDescription()).toEqual("- 50 damage on target\n- limit shield to 200 for 3 turns on target");
equipment.target_effects.push(new StickyEffect(new AttributeLimitEffect("shield_capacity", 200), 3));
expect(equipment.getActionDescription()).toEqual("- 50 damage on target\n- limit shield capacity to 200 for 3 turns on target");
});
});
}

View file

@ -29,7 +29,7 @@ module TS.SpaceTac.Game {
min_level: number;
// Minimal attribute to be able to equip this equipment
requirements: Attribute[];
requirements: { [key: string]: number };
// Action associated with this equipment
action: BaseAction;
@ -45,7 +45,7 @@ module TS.SpaceTac.Game {
this.slot = slot;
this.code = code;
this.name = code;
this.requirements = [];
this.requirements = {};
this.permanent_effects = [];
this.target_effects = [];
}
@ -57,8 +57,8 @@ module TS.SpaceTac.Game {
return false;
} else {
var able = true;
this.requirements.forEach((cap: Attribute) => {
if (ship.attributes.getValue(cap.code) < cap.current) {
iteritems(this.requirements, (attr, minvalue) => {
if (ship.getAttribute(<keyof ShipAttributes>attr) < minvalue) {
able = false;
}
});

View file

@ -8,8 +8,8 @@ module TS.SpaceTac.Game.Specs {
template.duration = new IntegerRange(1, 2);
template.ap_usage = new Range(4, 12);
template.min_level = new IntegerRange(5, 9);
template.addRequirement(AttributeCode.Cap_Energy, 2, 8);
template.addRequirement(AttributeCode.Cap_Human, 5);
template.addRequirement("skill_energy", 2, 8);
template.addRequirement("skill_human", 5);
var equipment = template.generateFixed(0.0);
@ -21,9 +21,10 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.duration).toEqual(1);
expect(equipment.ap_usage).toEqual(4);
expect(equipment.min_level).toEqual(5);
expect(equipment.requirements.length).toBe(2);
expect(equipment.requirements[0]).toEqual(new Attribute(AttributeCode.Cap_Energy, 2));
expect(equipment.requirements[1]).toEqual(new Attribute(AttributeCode.Cap_Human, 5));
expect(equipment.requirements).toEqual({
"skill_energy": 2,
"skill_human": 5
});
equipment = template.generateFixed(1.0);
@ -35,9 +36,10 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.duration).toEqual(2);
expect(equipment.ap_usage).toEqual(12);
expect(equipment.min_level).toEqual(9);
expect(equipment.requirements.length).toBe(2);
expect(equipment.requirements[0]).toEqual(new Attribute(AttributeCode.Cap_Energy, 8));
expect(equipment.requirements[1]).toEqual(new Attribute(AttributeCode.Cap_Human, 5));
expect(equipment.requirements).toEqual({
"skill_energy": 8,
"skill_human": 5
});
equipment = template.generateFixed(0.5);
@ -49,9 +51,10 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.duration).toEqual(2);
expect(equipment.ap_usage).toEqual(8);
expect(equipment.min_level).toEqual(7);
expect(equipment.requirements.length).toBe(2);
expect(equipment.requirements[0]).toEqual(new Attribute(AttributeCode.Cap_Energy, 5));
expect(equipment.requirements[1]).toEqual(new Attribute(AttributeCode.Cap_Human, 5));
expect(equipment.requirements).toEqual({
"skill_energy": 5,
"skill_human": 5
});
});
it("restricts power range to stay in a level range", () => {

View file

@ -7,8 +7,8 @@ module TS.SpaceTac.Game {
// Base name that will be given to generated equipment
name: string;
// Capability requirement ranges (indexed by AttributeCode)
requirements: IntegerRange[];
// Capability requirement ranges (indexed by attributes)
requirements: { [key: string]: IntegerRange };
// Distance to target
distance: Range;
@ -35,7 +35,7 @@ module TS.SpaceTac.Game {
constructor(slot: SlotType, name: string) {
this.slot = slot;
this.name = name;
this.requirements = [];
this.requirements = {};
this.distance = new Range(0, 0);
this.blast = new Range(0, 0);
this.duration = new IntegerRange(0, 0);
@ -46,7 +46,7 @@ module TS.SpaceTac.Game {
}
// Set a capability requirement
addRequirement(capability: AttributeCode, min: number, max: number = null): void {
addRequirement(capability: keyof ShipAttributes, min: number, max: number = null): void {
this.requirements[capability] = new IntegerRange(min, max);
}
@ -73,9 +73,9 @@ module TS.SpaceTac.Game {
result.action = this.getActionForEquipment(result);
this.requirements.forEach((requirement: IntegerRange, index: AttributeCode) => {
iteritems(this.requirements, (key: string, requirement: IntegerRange) => {
if (requirement) {
result.requirements.push(new Attribute(index, requirement.getProportional(power)));
result.requirements[key] = requirement.getProportional(power);
}
});
@ -124,23 +124,23 @@ module TS.SpaceTac.Game {
}
}
// Convenience function to add a permanent attribute effect on equipment
addPermanentAttributeValueEffect(code: AttributeCode, min: number, max: number = null): void {
var template = new EffectTemplate(new AttributeValueEffect(code, 0));
// Convenience function to add an attribute effect on equipment
addAttributeEffect(code: keyof ShipAttributes, min: number, max: number = null): void {
var template = new EffectTemplate(new AttributeEffect(code, 0));
template.addModifier("value", new IntegerRange(min, max));
this.permanent_effects.push(template);
}
// Convenience function to add a permanent attribute max effect on equipment
addPermanentAttributeMaxEffect(code: AttributeCode, min: number, max: number = null): void {
var template = new EffectTemplate(new AttributeMaxEffect(code, 0));
// Convenience function to add a permanent attribute limit effect on equipment
addAttributeLimitEffect(code: keyof ShipAttributes, min: number, max: number = null): void {
var template = new EffectTemplate(new AttributeLimitEffect(code, 0));
template.addModifier("value", new IntegerRange(min, max));
this.permanent_effects.push(template);
}
// Convenience function to add an offset effect on attribute value
addAttributeAddEffect(code: AttributeCode, min: number, max: number | null = null): void {
let template = new EffectTemplate(new AttributeAddEffect(code, 0));
// Convenience function to add a value effect on equipment
addValueEffectOnTarget(code: keyof ShipValues, min: number, max: number = null): void {
var template = new EffectTemplate(new ValueEffect(code, 0));
template.addModifier("value", new IntegerRange(min, max));
this.target_effects.push(template);
}

View file

@ -63,7 +63,7 @@ module TS.SpaceTac.Game {
let dy = target.y - this.ship.arena_y;
let distance = Math.sqrt(dx * dx + dy * dy);
let result = new MoveFireResult();
let ap = this.ship.ap_current.current;
let ap = this.ship.values.power.get();
if (distance > action.getRangeRadius(this.ship)) {
result.need_move = true;

View file

@ -65,32 +65,32 @@ module TS.SpaceTac.Game.Specs {
slot = ship.addSlot(SlotType.Power);
equipment = new Equipment();
equipment.slot = slot.type;
equipment.permanent_effects.push(new AttributeMaxEffect(AttributeCode.Power, 4));
equipment.permanent_effects.push(new AttributeEffect("power_capacity", 4));
slot.attach(equipment);
slot = ship.addSlot(SlotType.Power);
equipment = new Equipment();
equipment.slot = slot.type;
equipment.permanent_effects.push(new AttributeMaxEffect(AttributeCode.Power, 5));
equipment.permanent_effects.push(new AttributeEffect("power_capacity", 5));
slot.attach(equipment);
ship.updateAttributes();
expect(ship.ap_current.maximal).toBe(9);
expect(ship.attributes.power_capacity.get()).toBe(9);
});
it("repairs hull and recharges shield", function () {
var ship = new Ship(null, "Test");
ship.hull.setMaximal(120);
ship.shield.setMaximal(150);
ship.setAttribute("hull_capacity", 120);
ship.setAttribute("shield_capacity", 150);
expect(ship.hull.current).toEqual(0);
expect(ship.shield.current).toEqual(0);
expect(ship.values.hull.get()).toEqual(0);
expect(ship.values.shield.get()).toEqual(0);
ship.restoreHealth();
expect(ship.hull.current).toEqual(120);
expect(ship.shield.current).toEqual(150);
expect(ship.values.hull.get()).toEqual(120);
expect(ship.values.shield.get()).toEqual(150);
});
it("applies and logs hull and shield damage", function () {
@ -98,24 +98,24 @@ module TS.SpaceTac.Game.Specs {
var battle = new Battle(fleet);
var ship = new Ship(fleet);
ship.hull.setMaximal(50);
ship.shield.setMaximal(100);
ship.setAttribute("hull_capacity", 50);
ship.setAttribute("shield_capacity", 100);
ship.restoreHealth();
battle.log.clear();
ship.addDamage(10, 20);
expect(ship.hull.current).toEqual(40);
expect(ship.shield.current).toEqual(80);
expect(ship.values.hull.get()).toEqual(40);
expect(ship.values.shield.get()).toEqual(80);
expect(battle.log.events.length).toBe(3);
expect(battle.log.events[0]).toEqual(new AttributeChangeEvent(ship, ship.shield));
expect(battle.log.events[1]).toEqual(new AttributeChangeEvent(ship, ship.hull));
expect(battle.log.events[0]).toEqual(new ValueChangeEvent(ship, ship.values.shield));
expect(battle.log.events[1]).toEqual(new ValueChangeEvent(ship, ship.values.hull));
expect(battle.log.events[2]).toEqual(new DamageEvent(ship, 10, 20));
battle.log.clear();
ship.addDamage(15, 25, false);
expect(ship.hull.current).toEqual(25);
expect(ship.shield.current).toEqual(55);
expect(ship.values.hull.get()).toEqual(25);
expect(ship.values.shield.get()).toEqual(55);
expect(battle.log.events.length).toBe(0);
});
@ -164,13 +164,13 @@ module TS.SpaceTac.Game.Specs {
expect(ship.alive).toBe(true);
ship.hull.set(10);
ship.values.hull.set(10);
battle.log.clear();
ship.addDamage(5, 0);
expect(ship.alive).toBe(true);
expect(battle.log.events.length).toBe(2);
expect(battle.log.events[0].code).toEqual("attr");
expect(battle.log.events[0].code).toEqual("value");
expect(battle.log.events[1].code).toEqual("damage");
battle.log.clear();
@ -178,7 +178,7 @@ module TS.SpaceTac.Game.Specs {
expect(ship.alive).toBe(false);
expect(battle.log.events.length).toBe(3);
expect(battle.log.events[0].code).toEqual("attr");
expect(battle.log.events[0].code).toEqual("value");
expect(battle.log.events[1].code).toEqual("damage");
expect(battle.log.events[2].code).toEqual("death");
});
@ -189,12 +189,12 @@ module TS.SpaceTac.Game.Specs {
expect(ship.isAbleToPlay()).toBe(false);
expect(ship.isAbleToPlay(false)).toBe(true);
ship.ap_current.set(5);
ship.values.power.set(5);
expect(ship.isAbleToPlay()).toBe(true);
expect(ship.isAbleToPlay(false)).toBe(true);
ship.hull.set(10);
ship.values.hull.set(10);
ship.addDamage(8, 0);
expect(ship.isAbleToPlay()).toBe(true);
@ -243,16 +243,17 @@ module TS.SpaceTac.Game.Specs {
var power_core_template = new Equipments.BasicPowerCore();
ship.addSlot(SlotType.Power).attach(power_core_template.generateFixed(0));
ship.updateAttributes();
expect(ship.ap_current.current).toBe(0);
expect(ship.values.power.get()).toBe(0);
ship.initializeActionPoints();
expect(ship.ap_current.current).toBe(5);
ship.ap_current.set(2);
expect(ship.ap_current.current).toBe(2);
expect(ship.values.power.get()).toBe(5);
ship.values.power.set(2);
expect(ship.values.power.get()).toBe(2);
ship.recoverActionPoints();
expect(ship.ap_current.current).toBe(6);
expect(ship.values.power.get()).toBe(6);
ship.recoverActionPoints();
expect(ship.ap_current.current).toBe(8);
expect(ship.values.power.get()).toBe(8);
});
it("checks if a ship is inside a given circle", function () {

View file

@ -1,90 +1,104 @@
/// <reference path="ShipAttribute.ts"/>
/// <reference path="ShipValue.ts"/>
module TS.SpaceTac.Game {
// A single ship in a Fleet
/**
* Set of ShipAttribute for a ship
*/
export class ShipAttributes {
// Attribute controlling the play order
initiative = new ShipAttribute("initiative")
// Maximal hull value
hull_capacity = new ShipAttribute("hull capacity")
// Maximal shield value
shield_capacity = new ShipAttribute("shield capacity")
// Maximal power value
power_capacity = new ShipAttribute("power capacity")
// Initial power value at the start of a battle
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")
}
/**
* Set of ShipValue for a ship
*/
export class ShipValues {
hull = new ShipValue("hull")
shield = new ShipValue("shield")
power = new ShipValue("power")
}
/**
* Static attributes and values object for name queries
*/
export const SHIP_ATTRIBUTES = new ShipAttributes();
export const SHIP_VALUES = new ShipValues();
/**
* A single ship in a fleet
*/
export class Ship {
// Fleet this ship is a member of
fleet: Fleet;
fleet: Fleet
// Level of this ship
level: number;
level: number
// Name of the ship
name: string;
name: string
// Code of the ShipModel used to create it
model: string;
model: string
// Flag indicating if the ship is alive
alive: boolean;
alive: boolean
// Position in the arena
arena_x: number;
arena_y: number;
arena_x: number
arena_y: number
// Facing direction in the arena
arena_angle: number;
// Initiative (high numbers will allow this ship to play sooner)
initiative: Attribute;
// Current number of action points
ap_current: Attribute;
// Initial number of action points, at the start of a battle
ap_initial: Attribute;
// Number of action points recovered by turn
ap_recover: Attribute;
// Number of hull points (once it reaches 0, the ship is dead)
hull: Attribute;
// Number of shield points (a shield can absorb some damage to protect the hull)
shield: Attribute;
arena_angle: number
// Sticky effects that applies a given number of times
sticky_effects: StickyEffect[];
// Capabilities level
cap_material: Attribute;
cap_energy: Attribute;
cap_electronics: Attribute;
cap_human: Attribute;
cap_time: Attribute;
cap_gravity: Attribute;
sticky_effects: StickyEffect[]
// List of slots, able to contain equipment
slots: Slot[];
slots: Slot[]
// Collection of available attributes
attributes: AttributeCollection;
// Ship attributes
attributes = new ShipAttributes()
// Ship values
values = new ShipValues()
// Boolean set to true if the ship is currently playing its turn
playing = false;
playing = false
// Priority in play_order
play_priority = 0;
// Create a new ship inside a fleet
constructor(fleet: Fleet = null, name: string = null) {
this.attributes = new AttributeCollection();
this.fleet = fleet || new Fleet();
this.level = 1;
this.name = name;
this.model = "default";
this.alive = true;
this.initiative = this.newAttribute(AttributeCode.Initiative);
this.initiative.setMaximal(1);
this.ap_current = this.newAttribute(AttributeCode.Power);
this.ap_initial = this.newAttribute(AttributeCode.Power_Initial);
this.ap_recover = this.newAttribute(AttributeCode.Power_Recovery);
this.hull = this.newAttribute(AttributeCode.Hull);
this.shield = this.newAttribute(AttributeCode.Shield);
this.cap_material = this.newAttribute(AttributeCode.Cap_Material);
this.cap_energy = this.newAttribute(AttributeCode.Cap_Energy);
this.cap_electronics = this.newAttribute(AttributeCode.Cap_Electronics);
this.cap_human = this.newAttribute(AttributeCode.Cap_Human);
this.cap_time = this.newAttribute(AttributeCode.Cap_Time);
this.cap_gravity = this.newAttribute(AttributeCode.Cap_Gravity);
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;
@ -97,15 +111,10 @@ module TS.SpaceTac.Game {
// Returns true if the ship is able to play
// If *check_ap* is true, ap_current=0 will make this function return false
isAbleToPlay(check_ap: boolean = true): boolean {
var ap_checked = !check_ap || this.ap_current.current > 0;
var ap_checked = !check_ap || this.values.power.get() > 0;
return this.alive && ap_checked;
}
// Create and register an attribute
newAttribute(code: AttributeCode): Attribute {
return this.attributes.getRawAttr(code);
}
// Set position in the arena
// This does not consumes action points
setArenaPosition(x: number, y: number) {
@ -125,7 +134,7 @@ module TS.SpaceTac.Game {
// Make an initiative throw, to resolve play order in a battle
throwInitiative(gen: RandomGenerator): void {
this.initiative.set(gen.throw(this.initiative.maximal));
this.play_priority = gen.throw(this.attributes.initiative.get());
}
// Return the player owning this ship
@ -171,28 +180,68 @@ module TS.SpaceTac.Game {
}
/**
* Set an attribute value
* Get a ship value
*/
getValue(name: keyof ShipValues): number {
return this.values[name].get();
}
/**
* Set a ship value
*
* If *offset* is true, the value will be added to current value.
* If *log* is true, an attribute event will be added to the battle log
*
* Returns true if the attribute changed.
* Returns true if the value changed.
*/
setAttribute(attr: Attribute | AttributeCode, value: number, offset = false, log = true): boolean {
if (!(attr instanceof Attribute)) {
attr = this.attributes.getRawAttr(attr);
}
var changed: boolean;
setValue(name: keyof ShipValues, value: number, offset = false, log = true): boolean {
let changed: boolean;
let val = this.values[name];
if (offset) {
changed = attr.add(value);
changed = val.add(value);
} else {
changed = attr.set(value);
changed = val.set(value);
}
if (changed && log) {
this.addBattleEvent(new AttributeChangeEvent(this, attr));
this.addBattleEvent(new ValueChangeEvent(this, val));
}
return changed;
}
/**
* Get a ship attribute's current value
*/
getAttribute(name: keyof ShipAttributes): number {
return this.attributes[name].get();
}
/**
* Set a ship attribute
*
* If *log* is true, an attribute event will be added to the battle log
*
* Returns true if the value changed.
*/
setAttribute(name: keyof ShipAttributes, value: number, log = true): boolean {
let changed: boolean;
let attr = this.attributes[name];
changed = attr.set(value);
// TODO more generic
if (name == "power_capacity") {
this.values.power.setMaximal(attr.get());
} else if (name == "shield_capacity") {
this.values.shield.setMaximal(attr.get());
} else if (name == "hull_capacity") {
this.values.hull.setMaximal(attr.get());
}
if (changed && log) {
this.addBattleEvent(new ValueChangeEvent(this, attr));
}
return changed;
@ -203,9 +252,9 @@ module TS.SpaceTac.Game {
// If no value is provided, the attribute ap_initial will be used
initializeActionPoints(value: number = null): void {
if (value === null) {
value = this.ap_initial.current;
value = this.attributes.power_initial.get();
}
this.setAttribute(this.ap_current, value);
this.setValue("power", value);
}
// Recover action points
@ -213,14 +262,14 @@ module TS.SpaceTac.Game {
// If no value is provided, the current attribute ap_recovery will be used
recoverActionPoints(value: number = null): void {
if (value === null) {
value = this.ap_recover.current;
value = this.attributes.power_recovery.get();
}
this.setAttribute(this.ap_current, value, true);
this.setValue("power", value, true);
}
// Consumes action points
useActionPoints(value: number): void {
this.setAttribute(this.ap_current, -value, true);
this.setValue("power", -value, true);
}
// Method called at the start of battle
@ -256,6 +305,7 @@ module TS.SpaceTac.Game {
this.playing = false;
// Recover action points for next turn
this.updateAttributes();
this.recoverActionPoints();
// Apply sticky effects
@ -317,14 +367,14 @@ module TS.SpaceTac.Game {
// Apply damages to hull and/or shield
addDamage(hull: number, shield: number, log: boolean = true): void {
this.setAttribute(this.shield, -shield, true, log);
this.setAttribute(this.hull, -hull, true, log);
this.setValue("shield", -shield, true, log);
this.setValue("hull", -hull, true, log);
if (log) {
this.addBattleEvent(new DamageEvent(this, hull, shield));
}
if (this.hull.current === 0) {
if (this.values.hull.get() === 0) {
// Ship is dead
this.setDead(log);
}
@ -384,47 +434,49 @@ module TS.SpaceTac.Game {
// Update attributes, taking into account attached equipment and active effects
updateAttributes(): void {
// TODO Something more generic
// Compute new maximal values for attributes
var new_attrs = new AttributeCollection();
this.collectEffects("attrmax").forEach((effect: AttributeMaxEffect) => {
new_attrs.addValue(effect.attrcode, effect.value);
// Sum all attribute effects
var new_attrs = new ShipAttributes();
this.collectEffects("attr").forEach((effect: AttributeEffect) => {
new_attrs[effect.attrcode].add(effect.value);
});
this.initiative.setMaximal(new_attrs.getValue(AttributeCode.Initiative));
this.ap_current.setMaximal(new_attrs.getValue(AttributeCode.Power));
this.hull.setMaximal(new_attrs.getValue(AttributeCode.Hull));
this.shield.setMaximal(new_attrs.getValue(AttributeCode.Shield));
// Compute new current values for attributes
new_attrs = new AttributeCollection();
this.collectEffects("attr").forEach((effect: AttributeMaxEffect) => {
new_attrs.addValue(effect.attrcode, effect.value);
// Apply limit attributes
this.collectEffects("attrlimit").forEach((effect: AttributeLimitEffect) => {
new_attrs[effect.attrcode].setMaximal(effect.value);
});
// TODO better typing
iteritems(<any>new_attrs, (key, value) => {
this.setAttribute(<keyof ShipAttributes>key, (<ShipAttribute>value).get());
});
this.ap_initial.set(new_attrs.getValue(AttributeCode.Power_Initial));
this.ap_recover.set(new_attrs.getValue(AttributeCode.Power_Recovery));
}
// Fully restore hull and shield
restoreHealth(): void {
this.hull.set(this.hull.maximal);
this.shield.set(this.shield.maximal);
this.values.hull.set(this.attributes.hull_capacity.get());
this.values.shield.set(this.attributes.shield_capacity.get());
}
// Collect all effects to apply for updateAttributes
private collectEffects(code: string = null): BaseEffect[] {
private collectEffects(code: string): BaseEffect[] {
var result: BaseEffect[] = [];
this.slots.forEach((slot: Slot) => {
this.slots.forEach(slot => {
if (slot.attached) {
slot.attached.permanent_effects.forEach((effect: BaseEffect) => {
if (effect.code === code) {
slot.attached.permanent_effects.forEach(effect => {
if (effect.code == code) {
result.push(effect);
}
});
}
});
this.sticky_effects.forEach(effect => {
if (effect.base.code == code) {
result.push(effect.base);
}
});
return result;
}
}

16
src/game/ShipAttribute.ts Normal file
View file

@ -0,0 +1,16 @@
/// <reference path="ShipValue.ts"/>
module TS.SpaceTac.Game {
/**
* A ship attribute is a value computed by a sum of contributions from equipments and sticky effects.
*
* A value may be limited by other effects.
*/
export class ShipAttribute extends ShipValue {
// Raw contributions value (without limits)
private raw = 0
// Temporary limits
private limits: number[] = []
}
}

View file

@ -0,0 +1,72 @@
module TS.SpaceTac.Game {
describe("ShipValue", function () {
it("is initially not limited", function () {
var attr = new ShipValue("test");
attr.set(8888888);
expect(attr.get()).toBe(8888888);
});
it("applies minimal and maximal value", function () {
var attr = new ShipValue("test", 50, 100);
expect(attr.get()).toBe(50);
attr.add(8);
expect(attr.get()).toBe(58);
attr.add(60);
expect(attr.get()).toBe(100);
attr.add(-72);
expect(attr.get()).toBe(28);
attr.add(-60);
expect(attr.get()).toBe(0);
attr.set(8);
expect(attr.get()).toBe(8);
attr.set(-4);
expect(attr.get()).toBe(0);
attr.set(105);
expect(attr.get()).toBe(100);
attr.setMaximal(50);
expect(attr.get()).toBe(50);
attr.setMaximal(80);
expect(attr.get()).toBe(50);
});
it("tells if value changed", function () {
var result: boolean;
var attr = new ShipValue("test", 50, 100);
expect(attr.get()).toBe(50);
result = attr.set(51);
expect(result).toBe(true);
result = attr.set(51);
expect(result).toBe(false);
result = attr.add(1);
expect(result).toBe(true);
result = attr.add(0);
expect(result).toBe(false);
result = attr.add(1000);
expect(result).toBe(true);
result = attr.add(2000);
expect(result).toBe(false);
result = attr.set(-500);
expect(result).toBe(true);
result = attr.add(-600);
expect(result).toBe(false);
});
});
}

72
src/game/ShipValue.ts Normal file
View file

@ -0,0 +1,72 @@
module TS.SpaceTac.Game {
/**
* A ship value is a number that may vary and be constrained in a given range.
*/
export class ShipValue {
// Name of the value
name: string
// Current value
private current: number
// Upper bound
private maximal: number | null
constructor(code: string, current = 0, maximal: number | null = null) {
this.name = code;
this.current = current;
this.maximal = maximal;
}
/**
* Get the current value
*/
get(): number {
return this.current;
}
/**
* Set the upper bound the value must not cross
*/
setMaximal(value: number): void {
this.maximal = value;
this.fix();
}
/**
* Set an absolute value
*
* Returns true if the value changed
*/
set(value: number): boolean {
var old_value = this.current;
this.current = value;
this.fix();
return this.current !== old_value;
}
/**
* Add an offset to current value
*
* Returns true if the value changed
*/
add(value: number): boolean {
var old_value = this.current;
this.current += value;
this.fix();
return this.current !== old_value;
}
/**
* Fix the value to be positive and lower than maximal
*/
private fix(): void {
if (this.maximal !== null && this.current > this.maximal) {
this.current = this.maximal;
}
if (this.current < 0) {
this.current = 0;
}
}
}
}

View file

@ -23,13 +23,13 @@ module TS.SpaceTac.Game.Specs {
var equipment = new Equipment();
equipment.slot = SlotType.Shield;
equipment.requirements.push(new Attribute(AttributeCode.Cap_Gravity, 5));
equipment.requirements["skill_gravity"] = 5;
expect(slot.attached).toBeNull();
slot.attach(equipment);
expect(slot.attached).toBeNull();
ship.cap_gravity.set(6);
ship.attributes.skill_gravity.set(6);
slot.attach(equipment);
expect(slot.attached).toBe(equipment);

View file

@ -46,23 +46,18 @@ module TS.SpaceTac.Game {
static setShipAP(ship: Ship, points: number, recovery: number = 0): void {
var equipment = this.getOrGenEquipment(ship, SlotType.Power, new Equipments.BasicPowerCore());
equipment.permanent_effects.forEach((effect: BaseEffect) => {
if (effect.code === "attrmax") {
var meffect = <AttributeMaxEffect>effect;
if (meffect.attrcode === AttributeCode.Power) {
meffect.value = points;
}
} else if (effect.code === "attr") {
var veffect = <AttributeValueEffect>effect;
if (veffect.attrcode === AttributeCode.Power_Recovery) {
veffect.value = recovery;
equipment.permanent_effects.forEach(effect => {
if (effect instanceof AttributeEffect) {
if (effect.attrcode === "power_capacity") {
effect.value = points;
} else if (effect.attrcode === "power_recovery") {
effect.value = recovery;
}
}
});
ship.ap_current.setMaximal(points);
ship.ap_current.set(points);
ship.ap_recover.set(recovery);
ship.updateAttributes();
ship.setValue("power", points);
}
// Set a ship hull and shield points, adding/updating an equipment if needed
@ -70,19 +65,17 @@ module TS.SpaceTac.Game {
var armor = TestTools.getOrGenEquipment(ship, SlotType.Armor, new Equipments.IronHull());
var shield = TestTools.getOrGenEquipment(ship, SlotType.Shield, new Equipments.BasicForceField());
armor.permanent_effects.forEach((effect: BaseEffect) => {
if (effect.code === "attrmax") {
var meffect = <AttributeMaxEffect>effect;
if (meffect.attrcode === AttributeCode.Hull) {
meffect.value = hull_points;
armor.permanent_effects.forEach(effect => {
if (effect instanceof AttributeEffect) {
if (effect.attrcode === "hull_capacity") {
effect.value = hull_points;
}
}
});
shield.permanent_effects.forEach((effect: BaseEffect) => {
if (effect.code === "attrmax") {
var meffect = <AttributeMaxEffect>effect;
if (meffect.attrcode === AttributeCode.Shield) {
meffect.value = shield_points;
if (effect instanceof AttributeEffect) {
if (effect.attrcode === "shield_capacity") {
effect.value = shield_points;
}
}
});

View file

@ -6,22 +6,22 @@ module TS.SpaceTac.Game {
var action = new BaseAction("test", "Test", false, equipment);
var ship = new Ship();
ship.addSlot(SlotType.Armor).attach(equipment);
ship.ap_current.setMaximal(10);
ship.values.power.setMaximal(10);
expect(action.canBeUsed(null, ship)).toBe(false);
ship.ap_current.set(5);
ship.values.power.set(5);
expect(action.canBeUsed(null, ship)).toBe(true);
expect(action.canBeUsed(null, ship, 4)).toBe(true);
expect(action.canBeUsed(null, ship, 3)).toBe(true);
expect(action.canBeUsed(null, ship, 2)).toBe(false);
ship.ap_current.set(3);
ship.values.power.set(3);
expect(action.canBeUsed(null, ship)).toBe(true);
ship.ap_current.set(2);
ship.values.power.set(2);
expect(action.canBeUsed(null, ship)).toBe(false);
});

View file

@ -31,7 +31,7 @@ module TS.SpaceTac.Game {
// Check AP usage
if (remaining_ap === null) {
remaining_ap = ship.ap_current.current;
remaining_ap = ship.values.power.get();
}
var ap_usage = this.equipment ? this.equipment.ap_usage : 0;
return remaining_ap >= ap_usage;

View file

@ -58,7 +58,7 @@ module TS.SpaceTac.Game {
new DroneDeployedEvent(drone)
]);
expect(ship.ap_current.current).toEqual(1);
expect(ship.values.power.get()).toEqual(1);
});
});
}

View file

@ -4,8 +4,8 @@ module TS.SpaceTac.Game {
var ship = new Ship();
var battle = new Battle(ship.fleet);
battle.playing_ship = ship;
ship.ap_current.setMaximal(20);
ship.ap_current.set(6);
ship.values.power.setMaximal(20);
ship.values.power.set(6);
ship.arena_x = 0;
ship.arena_y = 0;
var engine = new Equipment();
@ -21,7 +21,7 @@ module TS.SpaceTac.Game {
result = action.checkTarget(battle, ship, Target.newFromLocation(0, 8));
expect(result).toEqual(Target.newFromLocation(0, 3));
ship.ap_current.set(0);
ship.values.power.set(0);
result = action.checkTarget(battle, ship, Target.newFromLocation(0, 8));
expect(result).toBeNull();
});
@ -41,8 +41,8 @@ module TS.SpaceTac.Game {
it("applies to ship location, battle log and AP", function () {
var ship = new Ship();
var battle = new Battle(ship.fleet);
ship.ap_current.setMaximal(20);
ship.ap_current.set(5);
ship.values.power.setMaximal(20);
ship.values.power.set(5);
ship.arena_x = 0;
ship.arena_y = 0;
var engine = new Equipment();
@ -55,13 +55,13 @@ module TS.SpaceTac.Game {
expect(result).toBe(true);
expect(ship.arena_x).toBeCloseTo(3.535533, 0.00001);
expect(ship.arena_y).toBeCloseTo(3.535533, 0.00001);
expect(ship.ap_current.current).toEqual(0);
expect(ship.values.power.get()).toEqual(0);
result = action.apply(battle, ship, Target.newFromLocation(10, 10));
expect(result).toBe(false);
expect(ship.arena_x).toBeCloseTo(3.535533, 0.00001);
expect(ship.arena_y).toBeCloseTo(3.535533, 0.00001);
expect(ship.ap_current.current).toEqual(0);
expect(ship.values.power.get()).toEqual(0);
expect(battle.log.events.length).toBe(2);
@ -71,10 +71,10 @@ module TS.SpaceTac.Game {
expect(battle.log.events[0].target.x).toBeCloseTo(3.535533, 0.00001);
expect(battle.log.events[0].target.y).toBeCloseTo(3.535533, 0.00001);
expect(battle.log.events[1].code).toEqual("attr");
expect(battle.log.events[1].code).toEqual("value");
expect(battle.log.events[1].ship).toBe(ship);
expect((<AttributeChangeEvent>battle.log.events[1]).attribute).toEqual(
new Attribute(AttributeCode.Power, 0, 20));
expect((<ValueChangeEvent>battle.log.events[1]).value).toEqual(
new ShipValue("power", 0, 20));
});
it("can't move too much near another ship", function () {

View file

@ -18,7 +18,7 @@ module TS.SpaceTac.Game {
// Check AP usage
if (remaining_ap === null) {
remaining_ap = ship.ap_current.current;
remaining_ap = ship.values.power.get();
}
return remaining_ap > 0.0001;
}
@ -33,7 +33,7 @@ module TS.SpaceTac.Game {
}
getRangeRadius(ship: Ship): number {
return ship.ap_current.current * this.equipment.distance / this.equipment.ap_usage;
return ship.values.power.get() * this.equipment.distance / this.equipment.ap_usage;
}
/**

View file

@ -43,8 +43,8 @@ module TS.SpaceTac.Game.AI.Specs {
engine.ap_usage = 3;
engine.distance = 1;
ship.addSlot(SlotType.Engine).attach(engine);
ship.ap_current.setMaximal(10);
ship.ap_current.set(8);
ship.values.power.setMaximal(10);
ship.values.power.set(8);
var enemy = new Ship();
var ai = new BullyAI(ship.fleet);
ai.ship = ship;
@ -54,7 +54,7 @@ module TS.SpaceTac.Game.AI.Specs {
weapon.distance = 3;
// enemy in range, the ship can fire without moving
ship.ap_current.set(8);
ship.values.power.set(8);
ship.arena_x = 1;
ship.arena_y = 0;
enemy.arena_x = 3;
@ -65,7 +65,7 @@ module TS.SpaceTac.Game.AI.Specs {
expect(result.fire.equipment).toBe(weapon);
// enemy out of range, but moving can bring it in range
ship.ap_current.set(8);
ship.values.power.set(8);
ship.arena_x = 1;
ship.arena_y = 0;
enemy.arena_x = 6;
@ -77,7 +77,7 @@ module TS.SpaceTac.Game.AI.Specs {
// enemy out of range, but moving can bring it in range, except for the safety margin
ai.move_margin = 0.1;
ship.ap_current.set(8);
ship.values.power.set(8);
ship.arena_x = 1;
ship.arena_y = 0;
enemy.arena_x = 6;
@ -87,7 +87,7 @@ module TS.SpaceTac.Game.AI.Specs {
ai.move_margin = 0;
// enemy totally out of range
ship.ap_current.set(8);
ship.values.power.set(8);
ship.arena_x = 1;
ship.arena_y = 0;
enemy.arena_x = 30;
@ -96,7 +96,7 @@ module TS.SpaceTac.Game.AI.Specs {
expect(result).toBeNull();
// enemy in range but not enough AP to fire
ship.ap_current.set(1);
ship.values.power.set(1);
ship.arena_x = 1;
ship.arena_y = 0;
enemy.arena_x = 3;
@ -105,7 +105,7 @@ module TS.SpaceTac.Game.AI.Specs {
expect(result).toBeNull();
// can move in range of enemy, but not enough AP to fire
ship.ap_current.set(7);
ship.values.power.set(7);
ship.arena_x = 1;
ship.arena_y = 0;
enemy.arena_x = 6;
@ -115,7 +115,7 @@ module TS.SpaceTac.Game.AI.Specs {
// no engine, can't move
ship.slots[0].attached.detach();
ship.ap_current.set(8);
ship.values.power.set(8);
ship.arena_x = 1;
ship.arena_y = 0;
enemy.arena_x = 6;
@ -152,8 +152,8 @@ module TS.SpaceTac.Game.AI.Specs {
weapon2.ap_usage = 1;
ai.ship.addSlot(SlotType.Weapon).attach(weapon2);
ai.ship.ap_current.setMaximal(10);
ai.ship.ap_current.set(8);
ai.ship.values.power.setMaximal(10);
ai.ship.values.power.set(8);
result = ai.listAllManeuvers();
expect(result.length).toBe(3);
@ -219,11 +219,11 @@ module TS.SpaceTac.Game.AI.Specs {
weapon.action = new FireWeaponAction(weapon);
ai.ship.addSlot(SlotType.Weapon).attach(weapon);
ai.ship.ap_current.setMaximal(10);
ai.ship.ap_current.set(6);
ai.ship.values.power.setMaximal(10);
ai.ship.values.power.set(6);
ship2.hull.set(15);
ship2.shield.set(10);
ship2.values.hull.set(15);
ship2.values.shield.set(10);
var move = ai.checkBullyManeuver(ship2, weapon);
expect(move).not.toBeNull();
@ -235,17 +235,17 @@ module TS.SpaceTac.Game.AI.Specs {
expect(battle.log.events.length).toBe(7);
expect(battle.log.events[0]).toEqual(new MoveEvent(ship1, 2, 0));
expect(battle.log.events[1]).toEqual(new AttributeChangeEvent(ship1,
new Attribute(AttributeCode.Power, 2, 10)));
expect(battle.log.events[1]).toEqual(new ValueChangeEvent(ship1,
new ShipValue("power", 2, 10)));
expect(battle.log.events[2]).toEqual(new FireEvent(ship1, weapon, Target.newFromShip(ship2)));
expect(battle.log.events[3]).toEqual(new AttributeChangeEvent(ship2,
new Attribute(AttributeCode.Shield, 0)));
expect(battle.log.events[4]).toEqual(new AttributeChangeEvent(ship2,
new Attribute(AttributeCode.Hull, 5)));
expect(battle.log.events[3]).toEqual(new ValueChangeEvent(ship2,
new ShipValue("shield", 0)));
expect(battle.log.events[4]).toEqual(new ValueChangeEvent(ship2,
new ShipValue("hull", 5)));
expect(battle.log.events[5]).toEqual(new DamageEvent(ship2, 10, 10));
expect(battle.log.events[6]).toEqual(new AttributeChangeEvent(ship1,
new Attribute(AttributeCode.Power, 1, 10)));
expect(battle.log.events[6]).toEqual(new ValueChangeEvent(ship1,
new ShipValue("power", 1, 10)));
});
});
}

View file

@ -105,7 +105,7 @@ module TS.SpaceTac.Game.AI {
var distance = target.getDistanceTo(Target.newFromShip(this.ship));
var move: Target;
var engine: Equipment;
var remaining_ap = this.ship.ap_current.current;
var remaining_ap = this.ship.values.power.get();
if (distance <= weapon.distance) {
// No need to move
move = null;

View file

@ -1,18 +0,0 @@
module TS.SpaceTac.Game {
describe("AttributeAddEffect", function () {
it("adds an amount to an attribute value", function () {
let effect = new AttributeAddEffect(AttributeCode.Shield, 20);
let ship = new Ship();
ship.shield.maximal = 80;
ship.setAttribute(AttributeCode.Shield, 55);
expect(ship.shield.current).toEqual(55);
effect.applyOnShip(ship);
expect(ship.shield.current).toEqual(75);
effect.applyOnShip(ship);
expect(ship.shield.current).toEqual(80);
});
});
}

View file

@ -1,35 +0,0 @@
/// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game {
/**
* Effect to add (or subtract if negative) an amount to an attribute value.
*
* The effect is "permanent", and will not be removed when the effect ends.
*/
export class AttributeAddEffect extends BaseEffect {
// Affected attribute
attrcode: AttributeCode;
// Value to add (or subtract if negative)
value: number;
constructor(attrcode: AttributeCode, value: number) {
super("attradd");
this.attrcode = attrcode;
this.value = value;
}
applyOnShip(ship: Ship): boolean {
return ship.setAttribute(this.attrcode, this.value, true);
}
isBeneficial(): boolean {
return this.value >= 0;
}
getFullCode(): string {
return this.code + "-" + AttributeCode[this.attrcode].toLowerCase().replace("_", "");
}
}
}

View file

View file

@ -0,0 +1,36 @@
/// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game {
/**
* Effect to modify an attribute.
*
* Attribute effects are stacking, and the value of an attribute is in fact the sum of all active attribute effects.
*/
export class AttributeEffect extends BaseEffect {
// Affected attribute
attrcode: keyof ShipAttributes;
// Base value
value: number;
constructor(attrcode: keyof ShipAttributes, value: number) {
super("attr");
this.attrcode = attrcode;
this.value = value;
}
applyOnShip(ship: Ship): boolean {
ship.updateAttributes();
return true;
}
isBeneficial(): boolean {
return this.value >= 0;
}
getFullCode(): string {
return this.code + "-" + this.attrcode;
}
}
}

View file

@ -1,16 +1,19 @@
/// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game {
// Hard limitation on attribute value
// For example, this could be used to slow a target by limiting its action points
/**
* Enforce a limitation on ship attribute final value
*
* For example, this could be used to slow a target by limiting its action points
*/
export class AttributeLimitEffect extends BaseEffect {
// Affected attribute
attrcode: AttributeCode;
attrcode: keyof ShipAttributes;
// Limit of the attribute value
value: number;
constructor(attrcode: AttributeCode, value: number = 0) {
constructor(attrcode: keyof ShipAttributes, value = 0) {
super("attrlimit");
this.attrcode = attrcode;
@ -18,19 +21,17 @@ module TS.SpaceTac.Game {
}
applyOnShip(ship: Ship): boolean {
var current = ship.attributes.getValue(this.attrcode);
if (current > this.value) {
ship.setAttribute(ship.attributes.getRawAttr(this.attrcode), this.value);
}
ship.updateAttributes();
return true;
}
getFullCode(): string {
return this.code + "-" + AttributeCode[this.attrcode].toLowerCase().replace("_", "");
return this.code + "-" + this.attrcode;
}
getDescription(): string {
return `limit ${ATTRIBUTE_NAMES[this.attrcode]} to ${this.value}`;
let attrname = SHIP_ATTRIBUTES[this.attrcode].name;
return `limit ${attrname} to ${this.value}`;
}
}
}

View file

@ -1,24 +0,0 @@
/// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game {
// Effect on attribute maximum
// Typically, these effects are summed up to define an attribute maximum
export class AttributeMaxEffect extends BaseEffect {
// Affected attribute
attrcode: AttributeCode;
// Value to add to the maximum
value: number;
constructor(attrcode: AttributeCode, value: number) {
super("attrmax");
this.attrcode = attrcode;
this.value = value;
}
getFullCode(): string {
return this.code + "-" + AttributeCode[this.attrcode].toLowerCase().replace("_", "");
}
}
}

View file

@ -1,24 +0,0 @@
/// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game {
// Effect on attribute value
// Typically, these effects are summed up to define an attribute value
export class AttributeValueEffect extends BaseEffect {
// Affected attribute
attrcode: AttributeCode;
// Value to contribute
value: number;
constructor(attrcode: AttributeCode, value: number) {
super("attr");
this.attrcode = attrcode;
this.value = value;
}
getFullCode(): string {
return this.code + "-" + AttributeCode[this.attrcode].toLowerCase().replace("_", "");
}
}
}

View file

@ -1,7 +1,11 @@
/// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game {
// Apply damage to a ship
/**
* Apply damage on a ship.
*
* Damage is applied on shield while there is some, then on the hull.
*/
export class DamageEffect extends BaseEffect {
// Base damage points
value: number;
@ -18,16 +22,16 @@ module TS.SpaceTac.Game {
var shield: number;
// Apply on shields
if (damage >= ship.shield.current) {
shield = ship.shield.current;
if (damage >= ship.values.shield.get()) {
shield = ship.values.shield.get();
} else {
shield = damage;
}
damage -= shield;
// Apply on hull
if (damage >= ship.hull.current) {
hull = ship.hull.current;
if (damage >= ship.values.hull.get()) {
hull = ship.values.hull.get();
} else {
hull = damage;
}

View file

@ -0,0 +1,18 @@
module TS.SpaceTac.Game {
describe("ValueEffect", function () {
it("adds an amount to a ship value", function () {
let effect = new ValueEffect("shield", 20);
let ship = new Ship();
ship.values.shield.setMaximal(80);
ship.setValue("shield", 55);
expect(ship.values.shield.get()).toEqual(55);
effect.applyOnShip(ship);
expect(ship.values.shield.get()).toEqual(75);
effect.applyOnShip(ship);
expect(ship.values.shield.get()).toEqual(80);
});
});
}

View file

@ -0,0 +1,35 @@
/// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game {
/**
* Effect to add (or subtract if negative) an amount to a ship value.
*
* The effect is immediate and permanent.
*/
export class ValueEffect extends BaseEffect {
// Affected value
valuetype: keyof ShipValues;
// Value to add (or subtract if negative)
value: number;
constructor(valuetype: keyof ShipValues, value: number) {
super("value");
this.valuetype = valuetype;
this.value = value;
}
applyOnShip(ship: Ship): boolean {
return ship.setValue(this.valuetype, this.value, true);
}
isBeneficial(): boolean {
return this.value >= 0;
}
getFullCode(): string {
return `${this.code}-${this.valuetype}`;
}
}
}

View file

@ -32,7 +32,7 @@ module TS.SpaceTac.Game.Specs {
it("can't fire without sufficient AP", function () {
var ship = new Ship();
ship.ap_current.set(3);
ship.values.power.set(3);
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 50);
@ -104,15 +104,15 @@ module TS.SpaceTac.Game.Specs {
var fleet2 = new Fleet(new Player());
var ship1 = new Ship(fleet1);
ship1.ap_current.set(50);
ship1.values.power.set(50);
var ship2 = new Ship(fleet2);
ship2.hull.setMaximal(100);
ship2.shield.setMaximal(30);
ship2.setAttribute("hull_capacity", 100);
ship2.setAttribute("shield_capacity", 30);
ship2.restoreHealth();
expect(ship2.hull.current).toEqual(100);
expect(ship2.shield.current).toEqual(30);
expect(ship2.values.hull.get()).toEqual(100);
expect(ship2.values.shield.get()).toEqual(30);
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 20);
weapon.ap_usage = new IntegerRange(1, 1);
@ -120,19 +120,19 @@ module TS.SpaceTac.Game.Specs {
var equipment = weapon.generateFixed(0);
equipment.action.apply(null, ship1, Target.newFromShip(ship2));
expect(ship2.hull.current).toEqual(100);
expect(ship2.shield.current).toEqual(10);
expect(ship1.ap_current.current).toEqual(49);
expect(ship2.values.hull.get()).toEqual(100);
expect(ship2.values.shield.get()).toEqual(10);
expect(ship1.values.power.get()).toEqual(49);
equipment.action.apply(null, ship1, Target.newFromShip(ship2));
expect(ship2.hull.current).toEqual(90);
expect(ship2.shield.current).toEqual(0);
expect(ship1.ap_current.current).toEqual(48);
expect(ship2.values.hull.get()).toEqual(90);
expect(ship2.values.shield.get()).toEqual(0);
expect(ship1.values.power.get()).toEqual(48);
equipment.action.apply(null, ship1, Target.newFromShip(ship2));
expect(ship2.hull.current).toEqual(70);
expect(ship2.shield.current).toEqual(0);
expect(ship1.ap_current.current).toEqual(47);
expect(ship2.values.hull.get()).toEqual(70);
expect(ship2.values.shield.get()).toEqual(0);
expect(ship1.values.power.get()).toEqual(47);
});
});
}

View file

@ -7,7 +7,7 @@ module TS.SpaceTac.Game.Equipments {
this.min_level = new IntegerRange(1, 3);
this.addPermanentAttributeMaxEffect(AttributeCode.Shield, 100, 200);
this.addAttributeEffect("shield_capacity", 100, 200);
}
}
}

View file

@ -7,10 +7,10 @@ module TS.SpaceTac.Game.Equipments {
this.min_level = new IntegerRange(1, 1);
this.addPermanentAttributeMaxEffect(AttributeCode.Initiative, 1);
this.addPermanentAttributeMaxEffect(AttributeCode.Power, 8);
this.addPermanentAttributeValueEffect(AttributeCode.Power_Initial, 5);
this.addPermanentAttributeValueEffect(AttributeCode.Power_Recovery, 4);
this.addAttributeEffect("initiative", 1);
this.addAttributeEffect("power_capacity", 8);
this.addAttributeEffect("power_initial", 5);
this.addAttributeEffect("power_recovery", 4);
}
}
}

View file

@ -10,7 +10,7 @@ module TS.SpaceTac.Game.Equipments {
this.distance = new Range(100, 100);
this.ap_usage = new IntegerRange(1);
this.addPermanentAttributeMaxEffect(AttributeCode.Initiative, 1);
this.addAttributeEffect("initiative", 1);
}
protected getActionForEquipment(equipment: Equipment): BaseAction {

View file

@ -7,7 +7,7 @@ module TS.SpaceTac.Game.Equipments {
this.min_level = new IntegerRange(1, 3);
this.addPermanentAttributeMaxEffect(AttributeCode.Hull, 100, 200);
this.addAttributeEffect("hull_capacity", 100, 200);
}
}
}

View file

@ -9,29 +9,29 @@ module TS.SpaceTac.Game.Specs {
TestTools.setShipAP(target, 7, 2);
spyOn(equipment.action, "canBeUsed").and.returnValue(true);
expect(target.ap_current.current).toBe(7);
expect(target.values.power.get()).toBe(7);
expect(target.sticky_effects).toEqual([]);
// Attribute is immediately limited
equipment.action.apply(null, ship, Target.newFromShip(target));
expect(target.ap_current.current).toBe(4);
expect(target.values.power.get()).toBe(4);
expect(target.sticky_effects).toEqual([
new StickyEffect(new AttributeLimitEffect(AttributeCode.Power, 4), 1, true, false)
new StickyEffect(new AttributeLimitEffect("power_capacity", 4), 1, true, false)
]);
// Attribute is limited for one turn, and prevents AP recovery
target.ap_current.set(6);
target.values.power.set(6);
target.recoverActionPoints();
target.startTurn();
expect(target.ap_current.current).toBe(4);
expect(target.values.power.get()).toBe(4);
expect(target.sticky_effects).toEqual([]);
// Effect vanished, so AP recovery happens
target.endTurn();
expect(target.ap_current.current).toBe(6);
expect(target.values.power.get()).toBe(6);
expect(target.sticky_effects).toEqual([]);
});
});

View file

@ -10,7 +10,7 @@ module TS.SpaceTac.Game.Equipments {
this.ap_usage = new IntegerRange(4, 5);
this.min_level = new IntegerRange(1, 3);
this.addSticky(new AttributeLimitEffect(AttributeCode.Power), 4, 3, 1, 2, true);
this.addSticky(new AttributeLimitEffect("power_capacity"), 4, 3, 1, 2, true);
}
}
}

View file

@ -4,7 +4,7 @@ module TS.SpaceTac.Game.Equipments {
let template = new RepairDrone();
let equipment = template.generateFixed(0);
expect(equipment.target_effects).toEqual([new AttributeAddEffect(AttributeCode.Hull, 10)]);
expect(equipment.target_effects).toEqual([new ValueEffect("hull", 10)]);
});
});
}

View file

@ -14,7 +14,7 @@ module TS.SpaceTac.Game.Equipments {
this.setEffectRadius(40, 80);
this.setPowerConsumption(4, 5);
this.addAttributeAddEffect(AttributeCode.Hull, 10, 20);
this.addValueEffectOnTarget("hull", 10, 20);
}
}
}

View file

@ -21,17 +21,17 @@ module TS.SpaceTac.Game.Specs {
(<DamageEffect>equipment.target_effects[0]).value = 20;
var checkHP = (h1: number, s1: number, h2: number, s2: number, h3: number, s3: number): void => {
expect(ship.hull.current).toBe(h1);
expect(ship.shield.current).toBe(s1);
expect(enemy1.hull.current).toBe(h2);
expect(enemy1.shield.current).toBe(s2);
expect(enemy2.hull.current).toBe(h3);
expect(enemy2.shield.current).toBe(s3);
expect(ship.values.hull.get()).toBe(h1);
expect(ship.values.shield.get()).toBe(s1);
expect(enemy1.values.hull.get()).toBe(h2);
expect(enemy1.values.shield.get()).toBe(s2);
expect(enemy2.values.hull.get()).toBe(h3);
expect(enemy2.values.shield.get()).toBe(s3);
};
checkHP(50, 30, 50, 30, 50, 30);
battle.log.clear();
battle.log.addFilter("attr");
battle.log.addFilter("value");
// Fire at a ship
var t = Target.newFromShip(enemy1);

View file

@ -1,15 +0,0 @@
/// <reference path="BaseLogEvent.ts"/>
module TS.SpaceTac.Game {
// Event logged when a ship moves
export class AttributeChangeEvent extends BaseLogEvent {
// Saved version of the attribute
attribute: Attribute;
constructor(ship: Ship, attribute: Attribute) {
super("attr", ship);
this.attribute = copy(attribute);
}
}
}

View file

@ -0,0 +1,15 @@
/// <reference path="BaseLogEvent.ts"/>
module TS.SpaceTac.Game {
// Event logged when a ship value or attribute changed
export class ValueChangeEvent extends BaseLogEvent {
// Saved version of the value
value: ShipValue;
constructor(ship: Ship, value: ShipValue) {
super("value", ship);
this.value = copy(value);
}
}
}

View file

@ -47,8 +47,8 @@ module TS.SpaceTac.View.Specs {
battleview.battle.playing_ship = ship;
battleview.player = ship.getPlayer();
ship.ap_current.setMaximal(10);
ship.ap_current.set(9);
ship.setAttribute("power_capacity", 10);
ship.setValue("power", 9);
bar.setShip(ship);
expect(bar.actions.length).toBe(4);
@ -75,19 +75,19 @@ module TS.SpaceTac.View.Specs {
bar.actionEnded();
// Not enough AP for both weapons
ship.ap_current.set(7);
ship.setValue("power", 7);
bar.actions[2].processClick();
checkFading([1, 2], [0, 3]);
bar.actionEnded();
// Not enough AP to move
ship.ap_current.set(3);
ship.setValue("power", 3);
bar.actions[1].processClick();
checkFading([0, 1, 2], [3]);
bar.actionEnded();
// Dynamic AP usage for move actions
ship.ap_current.set(6);
ship.setValue("power", 6);
bar.actions[0].processClick();
checkFading([], [0, 1, 2, 3]);
bar.actions[0].processHover(Game.Target.newFromLocation(2, 8));

View file

@ -65,8 +65,8 @@ module TS.SpaceTac.View {
// Update the action points indicator
updateActionPoints(): void {
if (this.ship) {
this.actionpoints.setValue(this.ship.ap_current.current, this.ship.ap_current.maximal);
this.actionpointstemp.setValue(this.ship.ap_current.current, this.ship.ap_current.maximal);
this.actionpoints.setValue(this.ship.values.power.get(), this.ship.attributes.power_capacity.get());
this.actionpointstemp.setValue(this.ship.values.power.get(), this.ship.attributes.power_capacity.get());
this.actionpoints.visible = true;
this.actionpointstemp.visible = true;
} else {
@ -78,7 +78,7 @@ module TS.SpaceTac.View {
// Update fading flags
// ap_usage is the consumption of currently selected action
updateFadings(ap_usage: number): void {
var remaining_ap = this.ship.ap_current.current - ap_usage;
var remaining_ap = this.ship.values.power.get() - ap_usage;
if (remaining_ap < 0) {
remaining_ap = 0;
}
@ -86,7 +86,7 @@ module TS.SpaceTac.View {
this.actions.forEach((icon: ActionIcon) => {
icon.updateFadingStatus(remaining_ap);
});
this.actionpointstemp.setValue(remaining_ap, this.ship.ap_current.maximal);
this.actionpointstemp.setValue(remaining_ap, this.ship.attributes.power_capacity.get());
}
// Set action icons from selected ship

View file

@ -143,7 +143,7 @@ module TS.SpaceTac.View {
}
this.setSelected(false);
this.updateActiveStatus();
this.updateFadingStatus(this.ship.ap_current.current);
this.updateFadingStatus(this.ship.values.power.get());
this.battleview.arena.range_hint.clearPrimary();
}

View file

@ -40,8 +40,8 @@ module TS.SpaceTac.View {
case "move":
this.processMoveEvent(<Game.MoveEvent>event);
break;
case "attr":
this.processAttributeChangedEvent(<Game.AttributeChangeEvent>event);
case "value":
this.processValueChangedEvent(<Game.ValueChangeEvent>event);
break;
case "death":
this.processDeathEvent(<Game.DeathEvent>event);
@ -97,12 +97,13 @@ module TS.SpaceTac.View {
}
}
// Ship attribute changed
private processAttributeChangedEvent(event: Game.AttributeChangeEvent): void {
// Ship value changed
private processValueChangedEvent(event: Game.ValueChangeEvent): void {
var item = this.view.ship_list.findItem(event.ship);
if (item) {
item.attributeChanged(event.attribute);
item.updateAttributes();
}
// TODO Update tooltip
}
// A ship died

View file

@ -4,15 +4,15 @@ module TS.SpaceTac.View {
// Reference to the ship game object
ship: Game.Ship;
// Energy display
energy: ValueBar;
// Hull display
hull: ValueBar;
// Shield display
shield: ValueBar;
// Power display
power: ValueBar;
// Portrait
layer_portrait: Phaser.Image;
@ -52,8 +52,8 @@ module TS.SpaceTac.View {
this.shield = ValueBar.newStyled(this.game, "battle-shiplist-shield", 98, 39, true);
this.addChild(this.shield);
this.energy = ValueBar.newStyled(this.game, "battle-shiplist-energy", 106, 39, true);
this.addChild(this.energy);
this.power = ValueBar.newStyled(this.game, "battle-shiplist-energy", 106, 39, true);
this.addChild(this.power);
this.updateAttributes();
this.updateEffects();
@ -63,9 +63,9 @@ module TS.SpaceTac.View {
// Update attributes from associated ship
updateAttributes() {
this.attributeChanged(this.ship.hull);
this.attributeChanged(this.ship.shield);
this.attributeChanged(this.ship.ap_current);
this.hull.setValue(this.ship.values.hull.get(), this.ship.attributes.hull_capacity.get());
this.shield.setValue(this.ship.values.shield.get(), this.ship.attributes.shield_capacity.get());
this.power.setValue(this.ship.values.power.get(), this.ship.attributes.power_capacity.get());
}
// Update effects applied on the ship
@ -81,17 +81,6 @@ module TS.SpaceTac.View {
});
}
// Called when an attribute for this ship changed through the battle log
attributeChanged(attribute: Game.Attribute): void {
if (attribute.code === Game.AttributeCode.Hull) {
this.hull.setValue(attribute.current, attribute.maximal);
} else if (attribute.code === Game.AttributeCode.Shield) {
this.shield.setValue(attribute.current, attribute.maximal);
} else if (attribute.code === Game.AttributeCode.Power) {
this.energy.setValue(attribute.current, attribute.maximal);
}
}
// Flash a damage indicator
setDamageHit() {
this.game.tweens.create(this.layer_damage).to({ alpha: 1 }, 100).to({ alpha: 0 }, 150).repeatAll(2).start();

View file

@ -92,15 +92,15 @@ module TS.SpaceTac.View {
// Fill info
this.title.setText(ship.name);
this.attr_hull.setText(ship.hull.current.toString());
this.attr_shield.setText(ship.shield.current.toString());
this.attr_power.setText(ship.ap_current.current.toString());
this.attr_materials.setText(ship.cap_material.current.toString());
this.attr_electronics.setText(ship.cap_electronics.current.toString());
this.attr_energy.setText(ship.cap_energy.current.toString());
this.attr_human.setText(ship.cap_human.current.toString());
this.attr_gravity.setText(ship.cap_gravity.current.toString());
this.attr_time.setText(ship.cap_time.current.toString());
this.attr_hull.setText(ship.values.hull.get().toString());
this.attr_shield.setText(ship.values.shield.get().toString());
this.attr_power.setText(ship.values.power.get().toString());
this.attr_materials.setText(ship.attributes.skill_material.get().toString());
this.attr_electronics.setText(ship.attributes.skill_electronics.get().toString());
this.attr_energy.setText(ship.attributes.skill_energy.get().toString());
this.attr_human.setText(ship.attributes.skill_human.get().toString());
this.attr_gravity.setText(ship.attributes.skill_gravity.get().toString());
this.attr_time.setText(ship.attributes.skill_time.get().toString());
this.active_effects.removeAll(true);
ship.sticky_effects.forEach((effect, index) => {
this.addEffect(effect, index);
@ -123,7 +123,7 @@ module TS.SpaceTac.View {
this.active_effects.addChild(effect_group);
if (effect.base instanceof Game.AttributeLimitEffect) {
let attr_name = Game.AttributeCode[effect.base.attrcode].toLowerCase().replace('_', '');
let attr_name = effect.base.attrcode.replace('_', '');
let attr_icon = new Phaser.Image(this.game, 30, effect_group.height / 2, `battle-attributes-${attr_name}`);
attr_icon.anchor.set(0.5, 0.5);
attr_icon.scale.set(0.17, 0.17);