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 * Restore serialization
* Refactor attribute effects (add/sub, value/max, temporary/permanent) * Drones: add hooks on game events
* Drones: add hooks on events
* Drones: add sprite, radius and tooltip * Drones: add sprite, radius and tooltip
* Repair drone: add graphics * Repair drone: add graphics and proper description
* Allow to cancel last moves * Allow to cancel last moves
* Effect should be random in a range (eg. "damage target 50-75") * Effect should be random in a range (eg. "damage target 50-75")
* Add an overload/cooling system * 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 fleet2 = new Fleet(null);
var ship1 = new Ship(fleet1, "F1S1"); var ship1 = new Ship(fleet1, "F1S1");
ship1.initiative.setMaximal(2); ship1.setAttribute("initiative", 2);
var ship2 = new Ship(fleet1, "F1S2"); var ship2 = new Ship(fleet1, "F1S2");
ship2.initiative.setMaximal(4); ship2.setAttribute("initiative", 4);
var ship3 = new Ship(fleet1, "F1S3"); var ship3 = new Ship(fleet1, "F1S3");
ship3.initiative.setMaximal(1); ship3.setAttribute("initiative", 1);
var ship4 = new Ship(fleet2, "F2S1"); var ship4 = new Ship(fleet2, "F2S1");
ship4.initiative.setMaximal(8); ship4.setAttribute("initiative", 8);
var ship5 = new Ship(fleet2, "F2S2"); var ship5 = new Ship(fleet2, "F2S2");
ship5.initiative.setMaximal(2); ship5.setAttribute("initiative", 2);
var battle = new Battle(fleet1, fleet2); var battle = new Battle(fleet1, fleet2);
expect(battle.play_order.length).toBe(0); expect(battle.play_order.length).toBe(0);

View file

@ -84,7 +84,7 @@ module TS.SpaceTac.Game {
// Sort by throw result // Sort by throw result
play_order.sort(function (ship1: Ship, ship2: Ship) { 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; this.play_order = play_order;
} }

View file

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

View file

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

View file

@ -6,28 +6,28 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.canBeEquipped(ship)).toBe(true); 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); expect(equipment.canBeEquipped(ship)).toBe(false);
ship.cap_time.set(1); ship.attributes.skill_time.set(1);
expect(equipment.canBeEquipped(ship)).toBe(false); expect(equipment.canBeEquipped(ship)).toBe(false);
ship.cap_time.set(2); ship.attributes.skill_time.set(2);
expect(equipment.canBeEquipped(ship)).toBe(true); expect(equipment.canBeEquipped(ship)).toBe(true);
ship.cap_time.set(3); ship.attributes.skill_time.set(3);
expect(equipment.canBeEquipped(ship)).toBe(true); expect(equipment.canBeEquipped(ship)).toBe(true);
// Second requirement // Second requirement
equipment.requirements.push(new Attribute(AttributeCode.Cap_Material, 3)); equipment.requirements["skill_material"] = 3;
expect(equipment.canBeEquipped(ship)).toBe(false); expect(equipment.canBeEquipped(ship)).toBe(false);
ship.cap_material.set(4); ship.attributes.skill_material.set(4);
expect(equipment.canBeEquipped(ship)).toBe(true); 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"); expect(equipment.getActionDescription()).toEqual("- 50 damage on all ships in 20km of impact");
equipment.blast = 0; equipment.blast = 0;
equipment.target_effects.push(new StickyEffect(new AttributeLimitEffect(AttributeCode.Shield, 200), 3)); equipment.target_effects.push(new StickyEffect(new AttributeLimitEffect("shield_capacity", 200), 3));
expect(equipment.getActionDescription()).toEqual("- 50 damage on target\n- limit shield to 200 for 3 turns on target"); 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; min_level: number;
// Minimal attribute to be able to equip this equipment // Minimal attribute to be able to equip this equipment
requirements: Attribute[]; requirements: { [key: string]: number };
// Action associated with this equipment // Action associated with this equipment
action: BaseAction; action: BaseAction;
@ -45,7 +45,7 @@ module TS.SpaceTac.Game {
this.slot = slot; this.slot = slot;
this.code = code; this.code = code;
this.name = code; this.name = code;
this.requirements = []; this.requirements = {};
this.permanent_effects = []; this.permanent_effects = [];
this.target_effects = []; this.target_effects = [];
} }
@ -57,8 +57,8 @@ module TS.SpaceTac.Game {
return false; return false;
} else { } else {
var able = true; var able = true;
this.requirements.forEach((cap: Attribute) => { iteritems(this.requirements, (attr, minvalue) => {
if (ship.attributes.getValue(cap.code) < cap.current) { if (ship.getAttribute(<keyof ShipAttributes>attr) < minvalue) {
able = false; able = false;
} }
}); });

View file

@ -8,8 +8,8 @@ module TS.SpaceTac.Game.Specs {
template.duration = new IntegerRange(1, 2); template.duration = new IntegerRange(1, 2);
template.ap_usage = new Range(4, 12); template.ap_usage = new Range(4, 12);
template.min_level = new IntegerRange(5, 9); template.min_level = new IntegerRange(5, 9);
template.addRequirement(AttributeCode.Cap_Energy, 2, 8); template.addRequirement("skill_energy", 2, 8);
template.addRequirement(AttributeCode.Cap_Human, 5); template.addRequirement("skill_human", 5);
var equipment = template.generateFixed(0.0); var equipment = template.generateFixed(0.0);
@ -21,9 +21,10 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.duration).toEqual(1); expect(equipment.duration).toEqual(1);
expect(equipment.ap_usage).toEqual(4); expect(equipment.ap_usage).toEqual(4);
expect(equipment.min_level).toEqual(5); expect(equipment.min_level).toEqual(5);
expect(equipment.requirements.length).toBe(2); expect(equipment.requirements).toEqual({
expect(equipment.requirements[0]).toEqual(new Attribute(AttributeCode.Cap_Energy, 2)); "skill_energy": 2,
expect(equipment.requirements[1]).toEqual(new Attribute(AttributeCode.Cap_Human, 5)); "skill_human": 5
});
equipment = template.generateFixed(1.0); equipment = template.generateFixed(1.0);
@ -35,9 +36,10 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.duration).toEqual(2); expect(equipment.duration).toEqual(2);
expect(equipment.ap_usage).toEqual(12); expect(equipment.ap_usage).toEqual(12);
expect(equipment.min_level).toEqual(9); expect(equipment.min_level).toEqual(9);
expect(equipment.requirements.length).toBe(2); expect(equipment.requirements).toEqual({
expect(equipment.requirements[0]).toEqual(new Attribute(AttributeCode.Cap_Energy, 8)); "skill_energy": 8,
expect(equipment.requirements[1]).toEqual(new Attribute(AttributeCode.Cap_Human, 5)); "skill_human": 5
});
equipment = template.generateFixed(0.5); equipment = template.generateFixed(0.5);
@ -49,9 +51,10 @@ module TS.SpaceTac.Game.Specs {
expect(equipment.duration).toEqual(2); expect(equipment.duration).toEqual(2);
expect(equipment.ap_usage).toEqual(8); expect(equipment.ap_usage).toEqual(8);
expect(equipment.min_level).toEqual(7); expect(equipment.min_level).toEqual(7);
expect(equipment.requirements.length).toBe(2); expect(equipment.requirements).toEqual({
expect(equipment.requirements[0]).toEqual(new Attribute(AttributeCode.Cap_Energy, 5)); "skill_energy": 5,
expect(equipment.requirements[1]).toEqual(new Attribute(AttributeCode.Cap_Human, 5)); "skill_human": 5
});
}); });
it("restricts power range to stay in a level range", () => { 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 // Base name that will be given to generated equipment
name: string; name: string;
// Capability requirement ranges (indexed by AttributeCode) // Capability requirement ranges (indexed by attributes)
requirements: IntegerRange[]; requirements: { [key: string]: IntegerRange };
// Distance to target // Distance to target
distance: Range; distance: Range;
@ -35,7 +35,7 @@ module TS.SpaceTac.Game {
constructor(slot: SlotType, name: string) { constructor(slot: SlotType, name: string) {
this.slot = slot; this.slot = slot;
this.name = name; this.name = name;
this.requirements = []; this.requirements = {};
this.distance = new Range(0, 0); this.distance = new Range(0, 0);
this.blast = new Range(0, 0); this.blast = new Range(0, 0);
this.duration = new IntegerRange(0, 0); this.duration = new IntegerRange(0, 0);
@ -46,7 +46,7 @@ module TS.SpaceTac.Game {
} }
// Set a capability requirement // 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); this.requirements[capability] = new IntegerRange(min, max);
} }
@ -73,9 +73,9 @@ module TS.SpaceTac.Game {
result.action = this.getActionForEquipment(result); result.action = this.getActionForEquipment(result);
this.requirements.forEach((requirement: IntegerRange, index: AttributeCode) => { iteritems(this.requirements, (key: string, requirement: IntegerRange) => {
if (requirement) { 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 // Convenience function to add an attribute effect on equipment
addPermanentAttributeValueEffect(code: AttributeCode, min: number, max: number = null): void { addAttributeEffect(code: keyof ShipAttributes, min: number, max: number = null): void {
var template = new EffectTemplate(new AttributeValueEffect(code, 0)); var template = new EffectTemplate(new AttributeEffect(code, 0));
template.addModifier("value", new IntegerRange(min, max)); template.addModifier("value", new IntegerRange(min, max));
this.permanent_effects.push(template); this.permanent_effects.push(template);
} }
// Convenience function to add a permanent attribute max effect on equipment // Convenience function to add a permanent attribute limit effect on equipment
addPermanentAttributeMaxEffect(code: AttributeCode, min: number, max: number = null): void { addAttributeLimitEffect(code: keyof ShipAttributes, min: number, max: number = null): void {
var template = new EffectTemplate(new AttributeMaxEffect(code, 0)); var template = new EffectTemplate(new AttributeLimitEffect(code, 0));
template.addModifier("value", new IntegerRange(min, max)); template.addModifier("value", new IntegerRange(min, max));
this.permanent_effects.push(template); this.permanent_effects.push(template);
} }
// Convenience function to add an offset effect on attribute value // Convenience function to add a value effect on equipment
addAttributeAddEffect(code: AttributeCode, min: number, max: number | null = null): void { addValueEffectOnTarget(code: keyof ShipValues, min: number, max: number = null): void {
let template = new EffectTemplate(new AttributeAddEffect(code, 0)); var template = new EffectTemplate(new ValueEffect(code, 0));
template.addModifier("value", new IntegerRange(min, max)); template.addModifier("value", new IntegerRange(min, max));
this.target_effects.push(template); this.target_effects.push(template);
} }

View file

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

View file

@ -65,32 +65,32 @@ module TS.SpaceTac.Game.Specs {
slot = ship.addSlot(SlotType.Power); slot = ship.addSlot(SlotType.Power);
equipment = new Equipment(); equipment = new Equipment();
equipment.slot = slot.type; 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.attach(equipment);
slot = ship.addSlot(SlotType.Power); slot = ship.addSlot(SlotType.Power);
equipment = new Equipment(); equipment = new Equipment();
equipment.slot = slot.type; 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); slot.attach(equipment);
ship.updateAttributes(); ship.updateAttributes();
expect(ship.ap_current.maximal).toBe(9); expect(ship.attributes.power_capacity.get()).toBe(9);
}); });
it("repairs hull and recharges shield", function () { it("repairs hull and recharges shield", function () {
var ship = new Ship(null, "Test"); var ship = new Ship(null, "Test");
ship.hull.setMaximal(120); ship.setAttribute("hull_capacity", 120);
ship.shield.setMaximal(150); ship.setAttribute("shield_capacity", 150);
expect(ship.hull.current).toEqual(0); expect(ship.values.hull.get()).toEqual(0);
expect(ship.shield.current).toEqual(0); expect(ship.values.shield.get()).toEqual(0);
ship.restoreHealth(); ship.restoreHealth();
expect(ship.hull.current).toEqual(120); expect(ship.values.hull.get()).toEqual(120);
expect(ship.shield.current).toEqual(150); expect(ship.values.shield.get()).toEqual(150);
}); });
it("applies and logs hull and shield damage", function () { it("applies and logs hull and shield damage", function () {
@ -98,24 +98,24 @@ module TS.SpaceTac.Game.Specs {
var battle = new Battle(fleet); var battle = new Battle(fleet);
var ship = new Ship(fleet); var ship = new Ship(fleet);
ship.hull.setMaximal(50); ship.setAttribute("hull_capacity", 50);
ship.shield.setMaximal(100); ship.setAttribute("shield_capacity", 100);
ship.restoreHealth(); ship.restoreHealth();
battle.log.clear(); battle.log.clear();
ship.addDamage(10, 20); ship.addDamage(10, 20);
expect(ship.hull.current).toEqual(40); expect(ship.values.hull.get()).toEqual(40);
expect(ship.shield.current).toEqual(80); expect(ship.values.shield.get()).toEqual(80);
expect(battle.log.events.length).toBe(3); expect(battle.log.events.length).toBe(3);
expect(battle.log.events[0]).toEqual(new AttributeChangeEvent(ship, ship.shield)); expect(battle.log.events[0]).toEqual(new ValueChangeEvent(ship, ship.values.shield));
expect(battle.log.events[1]).toEqual(new AttributeChangeEvent(ship, ship.hull)); expect(battle.log.events[1]).toEqual(new ValueChangeEvent(ship, ship.values.hull));
expect(battle.log.events[2]).toEqual(new DamageEvent(ship, 10, 20)); expect(battle.log.events[2]).toEqual(new DamageEvent(ship, 10, 20));
battle.log.clear(); battle.log.clear();
ship.addDamage(15, 25, false); ship.addDamage(15, 25, false);
expect(ship.hull.current).toEqual(25); expect(ship.values.hull.get()).toEqual(25);
expect(ship.shield.current).toEqual(55); expect(ship.values.shield.get()).toEqual(55);
expect(battle.log.events.length).toBe(0); expect(battle.log.events.length).toBe(0);
}); });
@ -164,13 +164,13 @@ module TS.SpaceTac.Game.Specs {
expect(ship.alive).toBe(true); expect(ship.alive).toBe(true);
ship.hull.set(10); ship.values.hull.set(10);
battle.log.clear(); battle.log.clear();
ship.addDamage(5, 0); ship.addDamage(5, 0);
expect(ship.alive).toBe(true); expect(ship.alive).toBe(true);
expect(battle.log.events.length).toBe(2); 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"); expect(battle.log.events[1].code).toEqual("damage");
battle.log.clear(); battle.log.clear();
@ -178,7 +178,7 @@ module TS.SpaceTac.Game.Specs {
expect(ship.alive).toBe(false); expect(ship.alive).toBe(false);
expect(battle.log.events.length).toBe(3); 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[1].code).toEqual("damage");
expect(battle.log.events[2].code).toEqual("death"); 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()).toBe(false);
expect(ship.isAbleToPlay(false)).toBe(true); expect(ship.isAbleToPlay(false)).toBe(true);
ship.ap_current.set(5); ship.values.power.set(5);
expect(ship.isAbleToPlay()).toBe(true); expect(ship.isAbleToPlay()).toBe(true);
expect(ship.isAbleToPlay(false)).toBe(true); expect(ship.isAbleToPlay(false)).toBe(true);
ship.hull.set(10); ship.values.hull.set(10);
ship.addDamage(8, 0); ship.addDamage(8, 0);
expect(ship.isAbleToPlay()).toBe(true); expect(ship.isAbleToPlay()).toBe(true);
@ -243,16 +243,17 @@ module TS.SpaceTac.Game.Specs {
var power_core_template = new Equipments.BasicPowerCore(); var power_core_template = new Equipments.BasicPowerCore();
ship.addSlot(SlotType.Power).attach(power_core_template.generateFixed(0)); 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(); ship.initializeActionPoints();
expect(ship.ap_current.current).toBe(5); expect(ship.values.power.get()).toBe(5);
ship.ap_current.set(2); ship.values.power.set(2);
expect(ship.ap_current.current).toBe(2); expect(ship.values.power.get()).toBe(2);
ship.recoverActionPoints(); ship.recoverActionPoints();
expect(ship.ap_current.current).toBe(6); expect(ship.values.power.get()).toBe(6);
ship.recoverActionPoints(); 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 () { 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 { 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 { export class Ship {
// Fleet this ship is a member of // Fleet this ship is a member of
fleet: Fleet; fleet: Fleet
// Level of this ship // Level of this ship
level: number; level: number
// Name of the ship // Name of the ship
name: string; name: string
// Code of the ShipModel used to create it // Code of the ShipModel used to create it
model: string; model: string
// Flag indicating if the ship is alive // Flag indicating if the ship is alive
alive: boolean; alive: boolean
// Position in the arena // Position in the arena
arena_x: number; arena_x: number
arena_y: number; arena_y: number
// Facing direction in the arena // Facing direction in the arena
arena_angle: number; 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;
// Sticky effects that applies a given number of times // Sticky effects that applies a given number of times
sticky_effects: StickyEffect[]; sticky_effects: StickyEffect[]
// Capabilities level
cap_material: Attribute;
cap_energy: Attribute;
cap_electronics: Attribute;
cap_human: Attribute;
cap_time: Attribute;
cap_gravity: Attribute;
// List of slots, able to contain equipment // List of slots, able to contain equipment
slots: Slot[]; slots: Slot[]
// Collection of available attributes // Ship attributes
attributes: AttributeCollection; attributes = new ShipAttributes()
// Ship values
values = new ShipValues()
// Boolean set to true if the ship is currently playing its turn // 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 // Create a new ship inside a fleet
constructor(fleet: Fleet = null, name: string = null) { constructor(fleet: Fleet = null, name: string = null) {
this.attributes = new AttributeCollection();
this.fleet = fleet || new Fleet(); this.fleet = fleet || new Fleet();
this.level = 1; this.level = 1;
this.name = name; this.name = name;
this.model = "default"; this.model = "default";
this.alive = true; 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.sticky_effects = [];
this.slots = []; this.slots = [];
this.attributes.initiative.set(1); // TODO Should not be needed
this.arena_x = 0; this.arena_x = 0;
this.arena_y = 0; this.arena_y = 0;
this.arena_angle = 0; this.arena_angle = 0;
@ -97,15 +111,10 @@ module TS.SpaceTac.Game {
// Returns true if the ship is able to play // Returns true if the ship is able to play
// If *check_ap* is true, ap_current=0 will make this function return false // If *check_ap* is true, ap_current=0 will make this function return false
isAbleToPlay(check_ap: boolean = true): boolean { 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; return this.alive && ap_checked;
} }
// Create and register an attribute
newAttribute(code: AttributeCode): Attribute {
return this.attributes.getRawAttr(code);
}
// Set position in the arena // Set position in the arena
// This does not consumes action points // This does not consumes action points
setArenaPosition(x: number, y: number) { setArenaPosition(x: number, y: number) {
@ -125,7 +134,7 @@ module TS.SpaceTac.Game {
// Make an initiative throw, to resolve play order in a battle // Make an initiative throw, to resolve play order in a battle
throwInitiative(gen: RandomGenerator): void { 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 // 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 *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 * 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 { setValue(name: keyof ShipValues, value: number, offset = false, log = true): boolean {
if (!(attr instanceof Attribute)) { let changed: boolean;
attr = this.attributes.getRawAttr(attr); let val = this.values[name];
}
var changed: boolean;
if (offset) { if (offset) {
changed = attr.add(value); changed = val.add(value);
} else { } else {
changed = attr.set(value); changed = val.set(value);
} }
if (changed && log) { 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; return changed;
@ -203,9 +252,9 @@ module TS.SpaceTac.Game {
// If no value is provided, the attribute ap_initial will be used // If no value is provided, the attribute ap_initial will be used
initializeActionPoints(value: number = null): void { initializeActionPoints(value: number = null): void {
if (value === null) { 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 // Recover action points
@ -213,14 +262,14 @@ module TS.SpaceTac.Game {
// If no value is provided, the current attribute ap_recovery will be used // If no value is provided, the current attribute ap_recovery will be used
recoverActionPoints(value: number = null): void { recoverActionPoints(value: number = null): void {
if (value === null) { 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 // Consumes action points
useActionPoints(value: number): void { useActionPoints(value: number): void {
this.setAttribute(this.ap_current, -value, true); this.setValue("power", -value, true);
} }
// Method called at the start of battle // Method called at the start of battle
@ -256,6 +305,7 @@ module TS.SpaceTac.Game {
this.playing = false; this.playing = false;
// Recover action points for next turn // Recover action points for next turn
this.updateAttributes();
this.recoverActionPoints(); this.recoverActionPoints();
// Apply sticky effects // Apply sticky effects
@ -317,14 +367,14 @@ module TS.SpaceTac.Game {
// Apply damages to hull and/or shield // Apply damages to hull and/or shield
addDamage(hull: number, shield: number, log: boolean = true): void { addDamage(hull: number, shield: number, log: boolean = true): void {
this.setAttribute(this.shield, -shield, true, log); this.setValue("shield", -shield, true, log);
this.setAttribute(this.hull, -hull, true, log); this.setValue("hull", -hull, true, log);
if (log) { if (log) {
this.addBattleEvent(new DamageEvent(this, hull, shield)); this.addBattleEvent(new DamageEvent(this, hull, shield));
} }
if (this.hull.current === 0) { if (this.values.hull.get() === 0) {
// Ship is dead // Ship is dead
this.setDead(log); this.setDead(log);
} }
@ -384,47 +434,49 @@ module TS.SpaceTac.Game {
// Update attributes, taking into account attached equipment and active effects // Update attributes, taking into account attached equipment and active effects
updateAttributes(): void { updateAttributes(): void {
// TODO Something more generic // Sum all attribute effects
var new_attrs = new ShipAttributes();
// Compute new maximal values for attributes this.collectEffects("attr").forEach((effect: AttributeEffect) => {
var new_attrs = new AttributeCollection(); new_attrs[effect.attrcode].add(effect.value);
this.collectEffects("attrmax").forEach((effect: AttributeMaxEffect) => {
new_attrs.addValue(effect.attrcode, 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 // Apply limit attributes
new_attrs = new AttributeCollection(); this.collectEffects("attrlimit").forEach((effect: AttributeLimitEffect) => {
this.collectEffects("attr").forEach((effect: AttributeMaxEffect) => { new_attrs[effect.attrcode].setMaximal(effect.value);
new_attrs.addValue(effect.attrcode, 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 // Fully restore hull and shield
restoreHealth(): void { restoreHealth(): void {
this.hull.set(this.hull.maximal); this.values.hull.set(this.attributes.hull_capacity.get());
this.shield.set(this.shield.maximal); this.values.shield.set(this.attributes.shield_capacity.get());
} }
// Collect all effects to apply for updateAttributes // Collect all effects to apply for updateAttributes
private collectEffects(code: string = null): BaseEffect[] { private collectEffects(code: string): BaseEffect[] {
var result: BaseEffect[] = []; var result: BaseEffect[] = [];
this.slots.forEach((slot: Slot) => { this.slots.forEach(slot => {
if (slot.attached) { if (slot.attached) {
slot.attached.permanent_effects.forEach((effect: BaseEffect) => { slot.attached.permanent_effects.forEach(effect => {
if (effect.code === code) { if (effect.code == code) {
result.push(effect); result.push(effect);
} }
}); });
} }
}); });
this.sticky_effects.forEach(effect => {
if (effect.base.code == code) {
result.push(effect.base);
}
});
return result; 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(); var equipment = new Equipment();
equipment.slot = SlotType.Shield; equipment.slot = SlotType.Shield;
equipment.requirements.push(new Attribute(AttributeCode.Cap_Gravity, 5)); equipment.requirements["skill_gravity"] = 5;
expect(slot.attached).toBeNull(); expect(slot.attached).toBeNull();
slot.attach(equipment); slot.attach(equipment);
expect(slot.attached).toBeNull(); expect(slot.attached).toBeNull();
ship.cap_gravity.set(6); ship.attributes.skill_gravity.set(6);
slot.attach(equipment); slot.attach(equipment);
expect(slot.attached).toBe(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 { 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.BasicPowerCore());
equipment.permanent_effects.forEach((effect: BaseEffect) => { equipment.permanent_effects.forEach(effect => {
if (effect.code === "attrmax") { if (effect instanceof AttributeEffect) {
var meffect = <AttributeMaxEffect>effect; if (effect.attrcode === "power_capacity") {
if (meffect.attrcode === AttributeCode.Power) { effect.value = points;
meffect.value = points; } else if (effect.attrcode === "power_recovery") {
} effect.value = recovery;
} else if (effect.code === "attr") {
var veffect = <AttributeValueEffect>effect;
if (veffect.attrcode === AttributeCode.Power_Recovery) {
veffect.value = recovery;
} }
} }
}); });
ship.ap_current.setMaximal(points); ship.updateAttributes();
ship.ap_current.set(points); ship.setValue("power", points);
ship.ap_recover.set(recovery);
} }
// Set a ship hull and shield points, adding/updating an equipment if needed // 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 armor = TestTools.getOrGenEquipment(ship, SlotType.Armor, new Equipments.IronHull());
var shield = TestTools.getOrGenEquipment(ship, SlotType.Shield, new Equipments.BasicForceField()); var shield = TestTools.getOrGenEquipment(ship, SlotType.Shield, new Equipments.BasicForceField());
armor.permanent_effects.forEach((effect: BaseEffect) => { armor.permanent_effects.forEach(effect => {
if (effect.code === "attrmax") { if (effect instanceof AttributeEffect) {
var meffect = <AttributeMaxEffect>effect; if (effect.attrcode === "hull_capacity") {
if (meffect.attrcode === AttributeCode.Hull) { effect.value = hull_points;
meffect.value = hull_points;
} }
} }
}); });
shield.permanent_effects.forEach((effect: BaseEffect) => { shield.permanent_effects.forEach((effect: BaseEffect) => {
if (effect.code === "attrmax") { if (effect instanceof AttributeEffect) {
var meffect = <AttributeMaxEffect>effect; if (effect.attrcode === "shield_capacity") {
if (meffect.attrcode === AttributeCode.Shield) { effect.value = shield_points;
meffect.value = shield_points;
} }
} }
}); });

View file

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

View file

@ -31,7 +31,7 @@ module TS.SpaceTac.Game {
// Check AP usage // Check AP usage
if (remaining_ap === null) { 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; var ap_usage = this.equipment ? this.equipment.ap_usage : 0;
return remaining_ap >= ap_usage; return remaining_ap >= ap_usage;

View file

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

View file

@ -18,7 +18,7 @@ module TS.SpaceTac.Game {
// Check AP usage // Check AP usage
if (remaining_ap === null) { if (remaining_ap === null) {
remaining_ap = ship.ap_current.current; remaining_ap = ship.values.power.get();
} }
return remaining_ap > 0.0001; return remaining_ap > 0.0001;
} }
@ -33,7 +33,7 @@ module TS.SpaceTac.Game {
} }
getRangeRadius(ship: Ship): number { 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.ap_usage = 3;
engine.distance = 1; engine.distance = 1;
ship.addSlot(SlotType.Engine).attach(engine); ship.addSlot(SlotType.Engine).attach(engine);
ship.ap_current.setMaximal(10); ship.values.power.setMaximal(10);
ship.ap_current.set(8); ship.values.power.set(8);
var enemy = new Ship(); var enemy = new Ship();
var ai = new BullyAI(ship.fleet); var ai = new BullyAI(ship.fleet);
ai.ship = ship; ai.ship = ship;
@ -54,7 +54,7 @@ module TS.SpaceTac.Game.AI.Specs {
weapon.distance = 3; weapon.distance = 3;
// enemy in range, the ship can fire without moving // 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_x = 1;
ship.arena_y = 0; ship.arena_y = 0;
enemy.arena_x = 3; enemy.arena_x = 3;
@ -65,7 +65,7 @@ module TS.SpaceTac.Game.AI.Specs {
expect(result.fire.equipment).toBe(weapon); expect(result.fire.equipment).toBe(weapon);
// enemy out of range, but moving can bring it in range // 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_x = 1;
ship.arena_y = 0; ship.arena_y = 0;
enemy.arena_x = 6; 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 // enemy out of range, but moving can bring it in range, except for the safety margin
ai.move_margin = 0.1; ai.move_margin = 0.1;
ship.ap_current.set(8); ship.values.power.set(8);
ship.arena_x = 1; ship.arena_x = 1;
ship.arena_y = 0; ship.arena_y = 0;
enemy.arena_x = 6; enemy.arena_x = 6;
@ -87,7 +87,7 @@ module TS.SpaceTac.Game.AI.Specs {
ai.move_margin = 0; ai.move_margin = 0;
// enemy totally out of range // enemy totally out of range
ship.ap_current.set(8); ship.values.power.set(8);
ship.arena_x = 1; ship.arena_x = 1;
ship.arena_y = 0; ship.arena_y = 0;
enemy.arena_x = 30; enemy.arena_x = 30;
@ -96,7 +96,7 @@ module TS.SpaceTac.Game.AI.Specs {
expect(result).toBeNull(); expect(result).toBeNull();
// enemy in range but not enough AP to fire // 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_x = 1;
ship.arena_y = 0; ship.arena_y = 0;
enemy.arena_x = 3; enemy.arena_x = 3;
@ -105,7 +105,7 @@ module TS.SpaceTac.Game.AI.Specs {
expect(result).toBeNull(); expect(result).toBeNull();
// can move in range of enemy, but not enough AP to fire // 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_x = 1;
ship.arena_y = 0; ship.arena_y = 0;
enemy.arena_x = 6; enemy.arena_x = 6;
@ -115,7 +115,7 @@ module TS.SpaceTac.Game.AI.Specs {
// no engine, can't move // no engine, can't move
ship.slots[0].attached.detach(); ship.slots[0].attached.detach();
ship.ap_current.set(8); ship.values.power.set(8);
ship.arena_x = 1; ship.arena_x = 1;
ship.arena_y = 0; ship.arena_y = 0;
enemy.arena_x = 6; enemy.arena_x = 6;
@ -152,8 +152,8 @@ module TS.SpaceTac.Game.AI.Specs {
weapon2.ap_usage = 1; weapon2.ap_usage = 1;
ai.ship.addSlot(SlotType.Weapon).attach(weapon2); ai.ship.addSlot(SlotType.Weapon).attach(weapon2);
ai.ship.ap_current.setMaximal(10); ai.ship.values.power.setMaximal(10);
ai.ship.ap_current.set(8); ai.ship.values.power.set(8);
result = ai.listAllManeuvers(); result = ai.listAllManeuvers();
expect(result.length).toBe(3); expect(result.length).toBe(3);
@ -219,11 +219,11 @@ module TS.SpaceTac.Game.AI.Specs {
weapon.action = new FireWeaponAction(weapon); weapon.action = new FireWeaponAction(weapon);
ai.ship.addSlot(SlotType.Weapon).attach(weapon); ai.ship.addSlot(SlotType.Weapon).attach(weapon);
ai.ship.ap_current.setMaximal(10); ai.ship.values.power.setMaximal(10);
ai.ship.ap_current.set(6); ai.ship.values.power.set(6);
ship2.hull.set(15); ship2.values.hull.set(15);
ship2.shield.set(10); ship2.values.shield.set(10);
var move = ai.checkBullyManeuver(ship2, weapon); var move = ai.checkBullyManeuver(ship2, weapon);
expect(move).not.toBeNull(); 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.length).toBe(7);
expect(battle.log.events[0]).toEqual(new MoveEvent(ship1, 2, 0)); expect(battle.log.events[0]).toEqual(new MoveEvent(ship1, 2, 0));
expect(battle.log.events[1]).toEqual(new AttributeChangeEvent(ship1, expect(battle.log.events[1]).toEqual(new ValueChangeEvent(ship1,
new Attribute(AttributeCode.Power, 2, 10))); new ShipValue("power", 2, 10)));
expect(battle.log.events[2]).toEqual(new FireEvent(ship1, weapon, Target.newFromShip(ship2))); expect(battle.log.events[2]).toEqual(new FireEvent(ship1, weapon, Target.newFromShip(ship2)));
expect(battle.log.events[3]).toEqual(new AttributeChangeEvent(ship2, expect(battle.log.events[3]).toEqual(new ValueChangeEvent(ship2,
new Attribute(AttributeCode.Shield, 0))); new ShipValue("shield", 0)));
expect(battle.log.events[4]).toEqual(new AttributeChangeEvent(ship2, expect(battle.log.events[4]).toEqual(new ValueChangeEvent(ship2,
new Attribute(AttributeCode.Hull, 5))); new ShipValue("hull", 5)));
expect(battle.log.events[5]).toEqual(new DamageEvent(ship2, 10, 10)); expect(battle.log.events[5]).toEqual(new DamageEvent(ship2, 10, 10));
expect(battle.log.events[6]).toEqual(new AttributeChangeEvent(ship1, expect(battle.log.events[6]).toEqual(new ValueChangeEvent(ship1,
new Attribute(AttributeCode.Power, 1, 10))); 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 distance = target.getDistanceTo(Target.newFromShip(this.ship));
var move: Target; var move: Target;
var engine: Equipment; var engine: Equipment;
var remaining_ap = this.ship.ap_current.current; var remaining_ap = this.ship.values.power.get();
if (distance <= weapon.distance) { if (distance <= weapon.distance) {
// No need to move // No need to move
move = null; 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"/> /// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game { 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 { export class AttributeLimitEffect extends BaseEffect {
// Affected attribute // Affected attribute
attrcode: AttributeCode; attrcode: keyof ShipAttributes;
// Limit of the attribute value // Limit of the attribute value
value: number; value: number;
constructor(attrcode: AttributeCode, value: number = 0) { constructor(attrcode: keyof ShipAttributes, value = 0) {
super("attrlimit"); super("attrlimit");
this.attrcode = attrcode; this.attrcode = attrcode;
@ -18,19 +21,17 @@ module TS.SpaceTac.Game {
} }
applyOnShip(ship: Ship): boolean { applyOnShip(ship: Ship): boolean {
var current = ship.attributes.getValue(this.attrcode); ship.updateAttributes();
if (current > this.value) {
ship.setAttribute(ship.attributes.getRawAttr(this.attrcode), this.value);
}
return true; return true;
} }
getFullCode(): string { getFullCode(): string {
return this.code + "-" + AttributeCode[this.attrcode].toLowerCase().replace("_", ""); return this.code + "-" + this.attrcode;
} }
getDescription(): string { 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"/> /// <reference path="BaseEffect.ts"/>
module TS.SpaceTac.Game { 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 { export class DamageEffect extends BaseEffect {
// Base damage points // Base damage points
value: number; value: number;
@ -18,16 +22,16 @@ module TS.SpaceTac.Game {
var shield: number; var shield: number;
// Apply on shields // Apply on shields
if (damage >= ship.shield.current) { if (damage >= ship.values.shield.get()) {
shield = ship.shield.current; shield = ship.values.shield.get();
} else { } else {
shield = damage; shield = damage;
} }
damage -= shield; damage -= shield;
// Apply on hull // Apply on hull
if (damage >= ship.hull.current) { if (damage >= ship.values.hull.get()) {
hull = ship.hull.current; hull = ship.values.hull.get();
} else { } else {
hull = damage; 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 () { it("can't fire without sufficient AP", function () {
var ship = new Ship(); var ship = new Ship();
ship.ap_current.set(3); ship.values.power.set(3);
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 50); 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 fleet2 = new Fleet(new Player());
var ship1 = new Ship(fleet1); var ship1 = new Ship(fleet1);
ship1.ap_current.set(50); ship1.values.power.set(50);
var ship2 = new Ship(fleet2); var ship2 = new Ship(fleet2);
ship2.hull.setMaximal(100); ship2.setAttribute("hull_capacity", 100);
ship2.shield.setMaximal(30); ship2.setAttribute("shield_capacity", 30);
ship2.restoreHealth(); ship2.restoreHealth();
expect(ship2.hull.current).toEqual(100); expect(ship2.values.hull.get()).toEqual(100);
expect(ship2.shield.current).toEqual(30); expect(ship2.values.shield.get()).toEqual(30);
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 20); var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 20);
weapon.ap_usage = new IntegerRange(1, 1); weapon.ap_usage = new IntegerRange(1, 1);
@ -120,19 +120,19 @@ module TS.SpaceTac.Game.Specs {
var equipment = weapon.generateFixed(0); var equipment = weapon.generateFixed(0);
equipment.action.apply(null, ship1, Target.newFromShip(ship2)); equipment.action.apply(null, ship1, Target.newFromShip(ship2));
expect(ship2.hull.current).toEqual(100); expect(ship2.values.hull.get()).toEqual(100);
expect(ship2.shield.current).toEqual(10); expect(ship2.values.shield.get()).toEqual(10);
expect(ship1.ap_current.current).toEqual(49); expect(ship1.values.power.get()).toEqual(49);
equipment.action.apply(null, ship1, Target.newFromShip(ship2)); equipment.action.apply(null, ship1, Target.newFromShip(ship2));
expect(ship2.hull.current).toEqual(90); expect(ship2.values.hull.get()).toEqual(90);
expect(ship2.shield.current).toEqual(0); expect(ship2.values.shield.get()).toEqual(0);
expect(ship1.ap_current.current).toEqual(48); expect(ship1.values.power.get()).toEqual(48);
equipment.action.apply(null, ship1, Target.newFromShip(ship2)); equipment.action.apply(null, ship1, Target.newFromShip(ship2));
expect(ship2.hull.current).toEqual(70); expect(ship2.values.hull.get()).toEqual(70);
expect(ship2.shield.current).toEqual(0); expect(ship2.values.shield.get()).toEqual(0);
expect(ship1.ap_current.current).toEqual(47); 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.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.min_level = new IntegerRange(1, 1);
this.addPermanentAttributeMaxEffect(AttributeCode.Initiative, 1); this.addAttributeEffect("initiative", 1);
this.addPermanentAttributeMaxEffect(AttributeCode.Power, 8); this.addAttributeEffect("power_capacity", 8);
this.addPermanentAttributeValueEffect(AttributeCode.Power_Initial, 5); this.addAttributeEffect("power_initial", 5);
this.addPermanentAttributeValueEffect(AttributeCode.Power_Recovery, 4); this.addAttributeEffect("power_recovery", 4);
} }
} }
} }

View file

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

View file

@ -7,7 +7,7 @@ module TS.SpaceTac.Game.Equipments {
this.min_level = new IntegerRange(1, 3); 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); TestTools.setShipAP(target, 7, 2);
spyOn(equipment.action, "canBeUsed").and.returnValue(true); 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([]); expect(target.sticky_effects).toEqual([]);
// Attribute is immediately limited // Attribute is immediately limited
equipment.action.apply(null, ship, Target.newFromShip(target)); 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([ 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 // Attribute is limited for one turn, and prevents AP recovery
target.ap_current.set(6); target.values.power.set(6);
target.recoverActionPoints(); target.recoverActionPoints();
target.startTurn(); target.startTurn();
expect(target.ap_current.current).toBe(4); expect(target.values.power.get()).toBe(4);
expect(target.sticky_effects).toEqual([]); expect(target.sticky_effects).toEqual([]);
// Effect vanished, so AP recovery happens // Effect vanished, so AP recovery happens
target.endTurn(); target.endTurn();
expect(target.ap_current.current).toBe(6); expect(target.values.power.get()).toBe(6);
expect(target.sticky_effects).toEqual([]); expect(target.sticky_effects).toEqual([]);
}); });
}); });

View file

@ -10,7 +10,7 @@ module TS.SpaceTac.Game.Equipments {
this.ap_usage = new IntegerRange(4, 5); this.ap_usage = new IntegerRange(4, 5);
this.min_level = new IntegerRange(1, 3); 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 template = new RepairDrone();
let equipment = template.generateFixed(0); 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.setEffectRadius(40, 80);
this.setPowerConsumption(4, 5); 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; (<DamageEffect>equipment.target_effects[0]).value = 20;
var checkHP = (h1: number, s1: number, h2: number, s2: number, h3: number, s3: number): void => { var checkHP = (h1: number, s1: number, h2: number, s2: number, h3: number, s3: number): void => {
expect(ship.hull.current).toBe(h1); expect(ship.values.hull.get()).toBe(h1);
expect(ship.shield.current).toBe(s1); expect(ship.values.shield.get()).toBe(s1);
expect(enemy1.hull.current).toBe(h2); expect(enemy1.values.hull.get()).toBe(h2);
expect(enemy1.shield.current).toBe(s2); expect(enemy1.values.shield.get()).toBe(s2);
expect(enemy2.hull.current).toBe(h3); expect(enemy2.values.hull.get()).toBe(h3);
expect(enemy2.shield.current).toBe(s3); expect(enemy2.values.shield.get()).toBe(s3);
}; };
checkHP(50, 30, 50, 30, 50, 30); checkHP(50, 30, 50, 30, 50, 30);
battle.log.clear(); battle.log.clear();
battle.log.addFilter("attr"); battle.log.addFilter("value");
// Fire at a ship // Fire at a ship
var t = Target.newFromShip(enemy1); 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.battle.playing_ship = ship;
battleview.player = ship.getPlayer(); battleview.player = ship.getPlayer();
ship.ap_current.setMaximal(10); ship.setAttribute("power_capacity", 10);
ship.ap_current.set(9); ship.setValue("power", 9);
bar.setShip(ship); bar.setShip(ship);
expect(bar.actions.length).toBe(4); expect(bar.actions.length).toBe(4);
@ -75,19 +75,19 @@ module TS.SpaceTac.View.Specs {
bar.actionEnded(); bar.actionEnded();
// Not enough AP for both weapons // Not enough AP for both weapons
ship.ap_current.set(7); ship.setValue("power", 7);
bar.actions[2].processClick(); bar.actions[2].processClick();
checkFading([1, 2], [0, 3]); checkFading([1, 2], [0, 3]);
bar.actionEnded(); bar.actionEnded();
// Not enough AP to move // Not enough AP to move
ship.ap_current.set(3); ship.setValue("power", 3);
bar.actions[1].processClick(); bar.actions[1].processClick();
checkFading([0, 1, 2], [3]); checkFading([0, 1, 2], [3]);
bar.actionEnded(); bar.actionEnded();
// Dynamic AP usage for move actions // Dynamic AP usage for move actions
ship.ap_current.set(6); ship.setValue("power", 6);
bar.actions[0].processClick(); bar.actions[0].processClick();
checkFading([], [0, 1, 2, 3]); checkFading([], [0, 1, 2, 3]);
bar.actions[0].processHover(Game.Target.newFromLocation(2, 8)); bar.actions[0].processHover(Game.Target.newFromLocation(2, 8));

View file

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

View file

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

View file

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

View file

@ -4,15 +4,15 @@ module TS.SpaceTac.View {
// Reference to the ship game object // Reference to the ship game object
ship: Game.Ship; ship: Game.Ship;
// Energy display
energy: ValueBar;
// Hull display // Hull display
hull: ValueBar; hull: ValueBar;
// Shield display // Shield display
shield: ValueBar; shield: ValueBar;
// Power display
power: ValueBar;
// Portrait // Portrait
layer_portrait: Phaser.Image; 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.shield = ValueBar.newStyled(this.game, "battle-shiplist-shield", 98, 39, true);
this.addChild(this.shield); this.addChild(this.shield);
this.energy = ValueBar.newStyled(this.game, "battle-shiplist-energy", 106, 39, true); this.power = ValueBar.newStyled(this.game, "battle-shiplist-energy", 106, 39, true);
this.addChild(this.energy); this.addChild(this.power);
this.updateAttributes(); this.updateAttributes();
this.updateEffects(); this.updateEffects();
@ -63,9 +63,9 @@ module TS.SpaceTac.View {
// Update attributes from associated ship // Update attributes from associated ship
updateAttributes() { updateAttributes() {
this.attributeChanged(this.ship.hull); this.hull.setValue(this.ship.values.hull.get(), this.ship.attributes.hull_capacity.get());
this.attributeChanged(this.ship.shield); this.shield.setValue(this.ship.values.shield.get(), this.ship.attributes.shield_capacity.get());
this.attributeChanged(this.ship.ap_current); this.power.setValue(this.ship.values.power.get(), this.ship.attributes.power_capacity.get());
} }
// Update effects applied on the ship // 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 // Flash a damage indicator
setDamageHit() { setDamageHit() {
this.game.tweens.create(this.layer_damage).to({ alpha: 1 }, 100).to({ alpha: 0 }, 150).repeatAll(2).start(); 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 // Fill info
this.title.setText(ship.name); this.title.setText(ship.name);
this.attr_hull.setText(ship.hull.current.toString()); this.attr_hull.setText(ship.values.hull.get().toString());
this.attr_shield.setText(ship.shield.current.toString()); this.attr_shield.setText(ship.values.shield.get().toString());
this.attr_power.setText(ship.ap_current.current.toString()); this.attr_power.setText(ship.values.power.get().toString());
this.attr_materials.setText(ship.cap_material.current.toString()); this.attr_materials.setText(ship.attributes.skill_material.get().toString());
this.attr_electronics.setText(ship.cap_electronics.current.toString()); this.attr_electronics.setText(ship.attributes.skill_electronics.get().toString());
this.attr_energy.setText(ship.cap_energy.current.toString()); this.attr_energy.setText(ship.attributes.skill_energy.get().toString());
this.attr_human.setText(ship.cap_human.current.toString()); this.attr_human.setText(ship.attributes.skill_human.get().toString());
this.attr_gravity.setText(ship.cap_gravity.current.toString()); this.attr_gravity.setText(ship.attributes.skill_gravity.get().toString());
this.attr_time.setText(ship.cap_time.current.toString()); this.attr_time.setText(ship.attributes.skill_time.get().toString());
this.active_effects.removeAll(true); this.active_effects.removeAll(true);
ship.sticky_effects.forEach((effect, index) => { ship.sticky_effects.forEach((effect, index) => {
this.addEffect(effect, index); this.addEffect(effect, index);
@ -123,7 +123,7 @@ module TS.SpaceTac.View {
this.active_effects.addChild(effect_group); this.active_effects.addChild(effect_group);
if (effect.base instanceof Game.AttributeLimitEffect) { 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}`); 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.anchor.set(0.5, 0.5);
attr_icon.scale.set(0.17, 0.17); attr_icon.scale.set(0.17, 0.17);