1
0
Fork 0

Refactored equipment/action system for leveled equipment generation

This commit is contained in:
Michaël Lemaire 2017-04-18 21:51:23 +02:00
parent b5be5575b3
commit 088ed18391
66 changed files with 909 additions and 888 deletions

2
TODO
View file

@ -5,6 +5,8 @@
* Character sheet: paginate loot and shop items
* Character sheet: improve eye-catching for shop and loot section
* Shops: add equipment pricing, with usage depreciation
* Add permanent effects to ship models to ease balancing
* Ships should start battle in formation to force them to move
* Fix targetting not resetting when using action shortcuts
* Add battle statistics and/or critics in outcome dialog
* Add battle experience

View file

@ -8,7 +8,7 @@
"build": "tsc -p .",
"pretest": "tsc -p .",
"test": "karma start spec/support/karma.conf.js && remap-istanbul -i out/coverage/coverage.json -o out/coverage -t html",
"prestart": "tsc -p .",
"prestart": "tsc -p . || true",
"start": "live-server out --host=127.0.0.1 --port=8012 --ignore=coverage",
"codecov": "remap-istanbul -i out/coverage/coverage.json -o out/coverage/mapped.json -t json && codecov -f out/coverage/mapped.json"
},

@ -1 +1 @@
Subproject commit 2b11ca44c3dcf368baa9099467842750aa176be7
Subproject commit 3092ff0f3b8af5fd1525480c5ec7033741cac100

View file

@ -35,8 +35,7 @@ module TS.SpaceTac.Specs {
// Force lucky finds with one template
var looter = new LootGenerator(random, false);
var template = new LootTemplate(SlotType.Power, "Nuclear Reactor");
template.min_level.set(3, 7);
template.distance.set(0, 5);
template.setSkillsRequirements({ "skill_energy": istep(4) });
looter.templates = [template];
spyOn(outcome, "getLootGenerator").and.returnValue(looter);
@ -45,11 +44,9 @@ module TS.SpaceTac.Specs {
expect(outcome.loot.length).toBe(3);
expect(outcome.loot[0].name).toBe("0a");
expect(outcome.loot[1].name).toBe("Nuclear Reactor");
expect(outcome.loot[1].min_level).toBe(4);
expect(outcome.loot[1].distance).toEqual(1);
expect(outcome.loot[1].requirements).toEqual({ "skill_energy": 7 });
expect(outcome.loot[2].name).toBe("Nuclear Reactor");
expect(outcome.loot[2].min_level).toBe(6);
expect(outcome.loot[2].distance).toBeCloseTo(4, 0.000001);
expect(outcome.loot[2].requirements).toEqual({ "skill_energy": 9 });
});
});
}

View file

@ -54,7 +54,7 @@ module TS.SpaceTac {
// The equipment will be in the dead ship range
generateLootItem(random: RandomGenerator, base_level: number): Equipment | null {
var generator = this.getLootGenerator(random);
var level = new IntegerRange(base_level - 1, base_level + 1);
var level = random.randInt(Math.max(base_level - 1, 1), base_level + 1);
return generator.generate(level);
}
}

26
src/core/Cooldown.spec.ts Normal file
View file

@ -0,0 +1,26 @@
module TS.SpaceTac.Specs {
describe("Cooldown", function () {
it("applies overheat and cooldown", function () {
let cooldown = new Cooldown();
expect(cooldown.canUse()).toBe(true);
cooldown.configure(2, 3);
expect(cooldown.canUse()).toBe(true);
cooldown.use();
expect(cooldown.canUse()).toBe(true);
cooldown.use();
expect(cooldown.canUse()).toBe(false);
cooldown.cool();
expect(cooldown.canUse()).toBe(false);
cooldown.cool();
expect(cooldown.canUse()).toBe(false);
cooldown.cool();
expect(cooldown.canUse()).toBe(true);
});
});
}

53
src/core/Cooldown.ts Normal file
View file

@ -0,0 +1,53 @@
module TS.SpaceTac {
/**
* Cooldown system for equipments
*/
export class Cooldown {
// Number of uses in the current turn
uses = 0
// Accumulated heat to dissipate (number of turns)
heat = 0
// Maximum number of uses allowed per turn before overheating (0 for unlimited)
overheat = 0
// Number of turns needed to cooldown when overheated
cooling = 0
/**
* Check if the equipment can be used in regards to heat
*/
canUse(): boolean {
return this.heat == 0;
}
/**
* Configure the overheat and cooling
*/
configure(overheat: number, cooling: number) {
this.overheat = overheat;
this.cooling = cooling;
}
/**
* Use the equipment, increasing the heat
*/
use(): void {
this.uses += 1;
if (this.uses >= this.overheat) {
this.heat = this.cooling;
}
}
/**
* Apply one cooling-down step if necessary
*/
cool(): void {
this.uses = 0;
if (this.heat > 0) {
this.heat -= 1;
}
}
}
}

View file

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

View file

@ -1,41 +0,0 @@
module TS.SpaceTac {
// Modifier for a value of a BaseEffect subclass
export class EffectTemplateModifier {
// Value name
name: string;
// Range of values (similar to ranges in LootTemplate)
range: Range;
// Basic constructor
constructor(name: string, range: Range) {
this.name = name;
this.range = range;
}
}
// Template used to generate a BaseEffect
export class EffectTemplate {
// Basic instance of the effect
effect: BaseEffect;
// Effect value modifiers
modifiers: EffectTemplateModifier[];
// Basic constructor
constructor(effect: BaseEffect) {
this.effect = effect;
this.modifiers = [];
}
// Add a value modifier for the effect
addModifier(name: string, range: Range) {
this.modifiers.push(new EffectTemplateModifier(name, range));
}
// Generate an effect with a given power
generateFixed(power: number): BaseEffect {
return this.effect.getModifiedCopy(this.modifiers, power);
}
}
}

View file

@ -33,19 +33,38 @@ module TS.SpaceTac.Specs {
});
it("generates a description of the effects", function () {
var equipment = new Equipment();
equipment.distance = 3;
let equipment = new Equipment();
expect(equipment.getActionDescription()).toEqual("does nothing");
equipment.target_effects.push(new DamageEffect(50));
expect(equipment.getActionDescription()).toEqual("- 50 damage on target");
let action = new FireWeaponAction(equipment, 1, 200, 0, [
new DamageEffect(50)
]);
equipment.action = action;
expect(equipment.getActionDescription()).toEqual("- Fire: 50 damage on target");
equipment.blast = 20;
expect(equipment.getActionDescription()).toEqual("- 50 damage in 20km radius");
action.blast = 20;
expect(equipment.getActionDescription()).toEqual("- Fire: 50 damage in 20km radius");
equipment.blast = 0;
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");
action.blast = 0;
action.effects.push(new StickyEffect(new AttributeLimitEffect("shield_capacity", 200), 3));
expect(equipment.getActionDescription()).toEqual("- Fire: 50 damage on target\n- Fire: limit shield capacity to 200 for 3 turns on target");
});
it("gets a minimal level, based on skills requirements", function () {
let equipment = new Equipment();
expect(equipment.getMinimumLevel()).toBe(1);
equipment.requirements["skill_human"] = 10;
expect(equipment.getMinimumLevel()).toBe(1);
equipment.requirements["skill_time"] = 1;
expect(equipment.getMinimumLevel()).toBe(2);
equipment.requirements["skill_gravity"] = 2;
expect(equipment.getMinimumLevel()).toBe(2);
equipment.requirements["skill_electronics"] = 4;
expect(equipment.getMinimumLevel()).toBe(3);
});
});
}

View file

@ -1,53 +1,37 @@
module TS.SpaceTac {
// Piece of equipment to attach in slots
export class Equipment {
// Type of slot this equipment can fit in
slot_type: SlotType | null;
// Actual slot this equipment is attached to
attached_to: Slot | null = null;
// Type of slot this equipment can fit in
slot: SlotType | null;
// Identifiable equipment code (may be used by UI to customize visual effects)
code: string;
// Equipment name
name: string;
// Maximal distance allowed to target
distance: number;
// Effect area's radius
blast: number;
// Duration
duration: number;
// Action Points usage
ap_usage: number;
// Level requirement
min_level: number;
// Minimal attribute to be able to equip this equipment
// Minimum skills to be able to equip this
requirements: { [key: string]: number };
// Action associated with this equipment
// Permanent effects on the ship that equips this
effects: BaseEffect[];
// Action available when equipped
action: BaseAction;
// Permanent effects on the ship that equips the equipment
permanent_effects: BaseEffect[];
// Effects on target
target_effects: BaseEffect[];
// Usage made of this equipment (will lower the sell price)
usage: number;
// Basic constructor
constructor(slot: SlotType | null = null, code = "equipment") {
this.slot = slot;
this.slot_type = slot;
this.code = code;
this.name = code;
this.requirements = {};
this.permanent_effects = [];
this.target_effects = [];
this.effects = [];
this.action = new BaseAction("nothing", "Do nothing", false);
}
@ -55,8 +39,23 @@ module TS.SpaceTac {
return this.attached_to ? `${this.attached_to.ship.name} - ${this.name}` : this.name;
}
// Returns true if the equipment can be equipped on a ship
// This checks *requirements* against the ship capabilities
/**
* Get the minimum level at which the requirements in skill may be fulfilled.
*
* This is informative and is not directly enforced. It will only be enforced by skills requirements.
*/
getMinimumLevel(): number {
let points = sum(values(this.requirements));
return ShipLevel.getLevelForPoints(points);
}
/**
* Returns true if the equipment can be equipped on a ship.
*
* This checks *requirements* against the ship skills.
*
* This does not check where the equipment currently is (except if is it already attached and should be detached first).
*/
canBeEquipped(ship: Ship): boolean {
if (this.attached_to) {
return false;
@ -71,7 +70,9 @@ module TS.SpaceTac {
}
}
// Detach from the slot it is attached to
/**
* Detach from the slot it is attached to
*/
detach(): void {
if (this.attached_to) {
this.attached_to.attached = null;
@ -79,21 +80,21 @@ module TS.SpaceTac {
}
}
// Get a human readable description of the effects of this equipment
/**
* Get a human readable description of the effects of this equipment
*/
getActionDescription(): string {
if (this.permanent_effects.length == 0 && this.target_effects.length == 0) {
return "does nothing";
} else {
var result: string[] = [];
this.target_effects.forEach(effect => {
let suffix = this.blast ? `in ${this.blast}km radius` : "on target";
if (effect instanceof StickyEffect) {
suffix = `for ${effect.duration} turn${effect.duration > 1 ? "s" : ""} ${suffix}`;
}
result.push("- " + effect.getDescription() + " " + suffix);
});
return result.join("\n");
}
let parts: string[] = [];
this.effects.forEach(effect => {
parts.push(`- Equip: ${effect.getDescription()}`);
});
this.action.getEffectsDescription().forEach(desc => {
parts.push(`- ${this.action.name}: ${desc}`);
});
return parts.length > 0 ? parts.join("\n") : "does nothing";
}
}
}

View file

@ -5,8 +5,7 @@ module TS.SpaceTac.Specs {
constructor() {
super(SlotType.Shield, "Hexagrid Shield");
this.min_level = new IntegerRange(2, 100);
this.ap_usage = new Range(6, 15);
this.setSkillsRequirements({ "skill_time": istep(2) });
}
}
@ -16,12 +15,11 @@ module TS.SpaceTac.Specs {
generator.templates = [new TestTemplate()];
generator.random = new SkewedRandomGenerator([0.5]);
var equipment = generator.generate(new IntegerRange(3, 6));
var equipment = generator.generate(2);
if (equipment) {
expect(equipment.slot).toBe(SlotType.Shield);
expect(equipment.slot_type).toBe(SlotType.Shield);
expect(equipment.name).toEqual("Hexagrid Shield");
expect(equipment.min_level).toBe(5);
expect(equipment.ap_usage).toBeCloseTo(6.2727, 0.00001);
expect(equipment.requirements).toEqual({ "skill_time": 3 });
} else {
fail("No equipment generated");
}

View file

@ -31,29 +31,15 @@ module TS.SpaceTac {
this.templates = templates;
}
// Generate a random equipment
// TODO Add generator from skills
// TODO Add generator of other qualities
// Generate a random equipment for a specific level
// If slot is specified, it will generate an equipment for this slot type specifically
// If level is specified, it will generate an equipment with level requirement inside this range
// If no equipment could be generated from available templates, null is returned
generate(level: IntegerRange | null = null, slot: SlotType | null = null): Equipment | null {
generate(level: number, slot: SlotType | null = null): Equipment | null {
// Generate equipments matching conditions, with each template
var equipments: Equipment[] = [];
this.templates.forEach((template: LootTemplate) => {
if (slot !== null && slot != template.slot) {
return;
}
var equipment: Equipment | null;
if (level) {
equipment = template.generateInLevelRange(level, this.random);
} else {
equipment = template.generate(this.random);
}
if (equipment) {
equipments.push(equipment);
}
});
let equipments = this.templates.filter(template => slot == null || slot == template.slot).map(template => template.generate(level));
// No equipment could be generated with given conditions
if (equipments.length === 0) {

View file

@ -1,123 +1,84 @@
/// <reference path="effects/BaseEffect.ts" />
module TS.SpaceTac.Specs {
class FakeEffect extends BaseEffect {
fakevalue: number
constructor(val = 5) {
super("fake");
this.fakevalue = val;
}
}
describe("LootTemplate", () => {
it("interpolates between weak and strong loot", () => {
var template = new LootTemplate(SlotType.Weapon, "Bulletator");
it("applies requirements on skills", function () {
let template = new LootTemplate(SlotType.Hull, "Hull");
template.setSkillsRequirements({ "skill_energy": 1, "skill_gravity": istep(2, istep(1)) });
template.distance = new Range(1, 3);
template.blast = new Range(1, 1);
template.duration = new IntegerRange(1, 2);
template.ap_usage = new Range(4, 12);
template.min_level = new IntegerRange(5, 9);
template.addRequirement("skill_energy", 2, 8);
template.addRequirement("skill_human", 5);
let result = template.generate(1);
expect(result.requirements).toEqual({
"skill_energy": 1,
"skill_gravity": 2
});
var equipment = template.generateFixed(0.0);
expect(equipment.slot).toEqual(SlotType.Weapon);
expect(equipment.code).toEqual("bulletator");
expect(equipment.name).toEqual("Bulletator");
expect(equipment.distance).toEqual(1);
expect(equipment.blast).toEqual(1);
expect(equipment.duration).toEqual(1);
expect(equipment.ap_usage).toEqual(4);
expect(equipment.min_level).toEqual(5);
expect(equipment.requirements).toEqual({
result = template.generate(2);
expect(result.requirements).toEqual({
"skill_energy": 2,
"skill_human": 5
"skill_gravity": 3
});
equipment = template.generateFixed(1.0);
expect(equipment.slot).toEqual(SlotType.Weapon);
expect(equipment.code).toEqual("bulletator");
expect(equipment.name).toEqual("Bulletator");
expect(equipment.distance).toEqual(3);
expect(equipment.blast).toEqual(1);
expect(equipment.duration).toEqual(2);
expect(equipment.ap_usage).toEqual(12);
expect(equipment.min_level).toEqual(9);
expect(equipment.requirements).toEqual({
"skill_energy": 8,
"skill_human": 5
});
equipment = template.generateFixed(0.5);
expect(equipment.slot).toEqual(SlotType.Weapon);
expect(equipment.code).toEqual("bulletator");
expect(equipment.name).toEqual("Bulletator");
expect(equipment.distance).toEqual(2);
expect(equipment.blast).toEqual(1);
expect(equipment.duration).toEqual(2);
expect(equipment.ap_usage).toEqual(8);
expect(equipment.min_level).toEqual(7);
expect(equipment.requirements).toEqual({
"skill_energy": 5,
"skill_human": 5
result = template.generate(10);
expect(result.requirements).toEqual({
"skill_energy": 10,
"skill_gravity": 47
});
});
it("restricts power range to stay in a level range", () => {
var template = new LootTemplate(SlotType.Weapon, "Bulletator");
template.min_level = new IntegerRange(4, 7);
it("applies attributes permenant effects", function () {
let template = new LootTemplate(SlotType.Shield, "Shield");
template.addAttributeEffect("shield_capacity", irange(undefined, 50, 10));
var result: any;
let result = template.generate(1);
expect(result.effects).toEqual([new AttributeEffect("shield_capacity", 50)]);
result = template.getPowerRangeForLevel(new IntegerRange(4, 7));
expect(result.min).toBe(0);
expect(result.max).toBe(1);
result = template.getPowerRangeForLevel(new IntegerRange(1, 10));
expect(result.min).toBe(0);
expect(result.max).toBe(1);
result = template.getPowerRangeForLevel(new IntegerRange(5, 6));
expect(result.min).toBeCloseTo(0.25, 0.000001);
expect(result.max).toBeCloseTo(0.75, 0.000001);
result = template.getPowerRangeForLevel(new IntegerRange(5, 12));
expect(result.min).toBeCloseTo(0.25, 0.000001);
expect(result.max).toBe(1);
result = template.getPowerRangeForLevel(new IntegerRange(3, 6));
expect(result.min).toBe(0);
expect(result.max).toBeCloseTo(0.75, 0.000001);
result = template.getPowerRangeForLevel(new IntegerRange(10, 15));
expect(result).toBeNull();
result = template.getPowerRangeForLevel(new IntegerRange(1, 3));
expect(result).toBeNull();
result = template.getPowerRangeForLevel(new IntegerRange(5, 5));
expect(result.min).toBeCloseTo(0.25, 0.000001);
expect(result.max).toBeCloseTo(0.5, 0.000001);
result = template.generate(2);
expect(result.effects).toEqual([new AttributeEffect("shield_capacity", 60)]);
});
it("adds modulated effects", () => {
let template = new LootTemplate(SlotType.Weapon, "Bulletator");
template.addEffect(new DamageEffect(), 5, 10, true);
it("adds move actions", function () {
let template = new LootTemplate(SlotType.Engine, "Engine");
template.addMoveAction(irange(undefined, 100, 10));
expect(template.generateFixed(0).target_effects).toEqual([new DamageEffect(5)]);
expect(template.generateFixed(1).target_effects).toEqual([new DamageEffect(10)]);
let result = template.generate(1);
expect(result.action).toEqual(new MoveAction(result, 100));
template.addEffect(new AttributeEffect("initiative"), 1, 2, false);
expect(template.generateFixed(0).permanent_effects).toEqual([new AttributeEffect("initiative", 1)]);
expect(template.generateFixed(1).permanent_effects).toEqual([new AttributeEffect("initiative", 2)]);
result = template.generate(2);
expect(result.action).toEqual(new MoveAction(result, 110));
});
it("adds modulated sticky effects", () => {
let template = new LootTemplate(SlotType.Weapon, "Bulletator");
template.addStickyEffect(new DamageEffect(), 5, 10, 1, 2, true, false, true);
it("adds fire actions", function () {
let template = new LootTemplate(SlotType.Weapon, "Weapon");
template.addFireAction(istep(1), istep(100), istep(50), [
new EffectTemplate(new FakeEffect(3), { "fakevalue": istep(8) })
]);
expect(template.generateFixed(0).target_effects).toEqual([new StickyEffect(new DamageEffect(5), 1, true, false)]);
expect(template.generateFixed(1).target_effects).toEqual([new StickyEffect(new DamageEffect(10), 2, true, false)]);
let result = template.generate(1);
expect(result.action).toEqual(new FireWeaponAction(result, 1, 100, 50, [new FakeEffect(8)]));
template.addStickyEffect(new AttributeLimitEffect("power_recovery"), 1, 2, 1, null, false, true, false);
result = template.generate(2);
expect(result.action).toEqual(new FireWeaponAction(result, 2, 101, 51, [new FakeEffect(9)]));
});
expect(template.generateFixed(0).permanent_effects).toEqual([new StickyEffect(new AttributeLimitEffect("power_recovery", 1), 1, false, true)]);
expect(template.generateFixed(1).permanent_effects).toEqual([new StickyEffect(new AttributeLimitEffect("power_recovery", 2), 1, false, true)]);
it("adds drone actions", function () {
let template = new LootTemplate(SlotType.Weapon, "Weapon");
template.addDroneAction(istep(1), istep(100), istep(2), istep(50), [
new EffectTemplate(new FakeEffect(3), { "fakevalue": istep(8) })
]);
let result = template.generate(1);
expect(result.action).toEqual(new DeployDroneAction(result, 1, 100, 2, 50, [new FakeEffect(8)]));
result = template.generate(2);
expect(result.action).toEqual(new DeployDroneAction(result, 2, 101, 3, 51, [new FakeEffect(9)]));
});
});
}

View file

@ -1,5 +1,93 @@
module TS.SpaceTac {
// Template used to generate a loot equipment
/**
* Quality of loot.
*/
export enum LootQuality {
WEAK,
COMMON,
FINE,
PREMIUM
}
/**
* A leveled value is either a number multiplied by the level, or a custom iterator
*/
type LeveledValue = number | Iterator<number>;
/**
* Resolve a leveled value
*/
function resolveForLevel(value: LeveledValue, level: number): number {
if (typeof value === "number") {
value *= level;
} else {
value = iat(value, level - 1) || 0;
}
return Math.floor(value);
}
/**
* Template used to generate a BaseEffect for a given ship level
*/
export class EffectTemplate<T extends BaseEffect> {
// Basic instance of the effect
effect: T;
// Effect value modifiers
modifiers: [keyof T, LeveledValue][];
constructor(effect: T, modifiers: { [attr: string]: LeveledValue }) {
this.effect = effect;
this.modifiers = [];
iteritems(modifiers, (key, value) => {
if (effect.hasOwnProperty(key)) {
this.addModifier(<keyof T>key, value);
}
});
}
// Add a value modifier for the effect
addModifier(name: keyof T, value: LeveledValue) {
this.modifiers.push([name, value]);
}
// Generate an effect with a given level
generate(level: number): T {
let result = copy(this.effect);
this.modifiers.forEach(modifier => {
let [name, value] = modifier;
(<any>result)[name] = resolveForLevel(value, level);
});
return result;
}
}
/**
* Template used to generate a BaseEffect for a given ship level
*/
export class StickyEffectTemplate<T extends BaseEffect> extends EffectTemplate<BaseEffect> {
duration: LeveledValue;
constructor(effect: T, modifiers: { [attr: string]: LeveledValue }, duration: LeveledValue) {
super(effect, modifiers);
this.duration = duration;
}
generate(level: number): StickyEffect {
let result = copy(this.effect);
this.modifiers.forEach(modifier => {
let [name, value] = modifier;
(<any>result)[name] = resolveForLevel(value, level);
});
return new StickyEffect(result, resolveForLevel(this.duration, level), true);
}
}
/**
* Template used to generate a loot equipment
*/
export class LootTemplate {
// Type of slot this equipment will fit in
slot: SlotType;
@ -7,181 +95,83 @@ module TS.SpaceTac {
// Base name that will be given to generated equipment
name: string;
// Capability requirement ranges (indexed by attributes)
requirements: { [key: string]: IntegerRange };
// Modifiers applied to obtain the "common" equipment, based on level
protected base_modifiers: ((equipment: Equipment, level: number) => void)[];
// Distance to target
distance: Range;
// Effect area's radius
blast: Range;
// Duration, in number of turns
duration: IntegerRange;
// Permanent effects (when attached to an equipment slot)
permanent_effects: EffectTemplate[];
// Effects on target
target_effects: EffectTemplate[];
// Action Points usage
ap_usage: Range;
// Level requirement
min_level: IntegerRange;
// Create a loot template
constructor(slot: SlotType, name: string) {
this.slot = slot;
this.name = name;
this.requirements = {};
this.distance = new Range(0, 0);
this.blast = new Range(0, 0);
this.duration = new IntegerRange(0, 0);
this.ap_usage = new IntegerRange(0, 0);
this.min_level = new IntegerRange(0, 0);
this.permanent_effects = [];
this.target_effects = [];
this.base_modifiers = [];
}
// Set a capability requirement
addRequirement(capability: keyof ShipAttributes, min: number, max: number | null = null): void {
this.requirements[capability] = new IntegerRange(min, max);
}
/**
* Generate a new equipment of a given level and quality
*/
generate(level: number, quality: LootQuality = LootQuality.COMMON, random = RandomGenerator.global): Equipment {
let result = new Equipment();
// Generate a random equipment with this template
generate(random = RandomGenerator.global): Equipment {
var power = random.random();
return this.generateFixed(power);
}
// Generate a fixed-power equipment with this template
generateFixed(power: number): Equipment {
var result = new Equipment();
result.slot = this.slot;
result.slot_type = this.slot;
result.code = (this.name || "").toLowerCase().replace(/ /g, "");
result.name = this.name;
result.distance = this.distance.getProportional(power);
result.blast = this.blast.getProportional(power);
result.duration = this.duration.getProportional(power);
result.ap_usage = this.ap_usage.getProportional(power);
result.min_level = this.min_level.getProportional(power);
let action = this.getActionForEquipment(result)
if (action) {
result.action = action;
}
iteritems(this.requirements, (key: string, requirement: IntegerRange) => {
if (requirement) {
result.requirements[key] = requirement.getProportional(power);
}
});
this.permanent_effects.forEach((eff_template: EffectTemplate) => {
result.permanent_effects.push(eff_template.generateFixed(power));
});
this.target_effects.forEach((eff_template: EffectTemplate) => {
result.target_effects.push(eff_template.generateFixed(power));
});
this.base_modifiers.forEach(modifier => modifier(result, level));
return result;
}
// Find the power range that will result in the level range
getPowerRangeForLevel(level: IntegerRange): Range | null {
if (level.min > this.min_level.max || level.max < this.min_level.min) {
return null;
} else {
var min: number;
var max: number;
/**
* Set skill requirements that will be added to each level of equipment.
*/
setSkillsRequirements(skills: { [skill: string]: LeveledValue }): void {
this.base_modifiers.push((equipment, level) => {
iteritems(skills, (skill, value) => {
let resolved = resolveForLevel(value, level);
if (resolved > 0) {
equipment.requirements[skill] = (equipment.requirements[skill] || 0) + resolved;
}
});
});
}
if (level.min <= this.min_level.min) {
min = 0.0;
} else {
min = this.min_level.getReverseProportional(level.min);
/**
* Add a permanent attribute effect, when the item is equipped.
*/
addAttributeEffect(attribute: keyof ShipAttributes, value: LeveledValue): void {
this.base_modifiers.push((equipment, level) => {
let resolved = resolveForLevel(value, level);
if (resolved > 0) {
equipment.effects.push(new AttributeEffect(attribute, resolved));
}
if (level.max >= this.min_level.max) {
max = 1.0;
} else {
max = this.min_level.getReverseProportional(level.max + 1);
}
return new Range(min, max);
}
}
// Generate an equipment that will have its level requirement in the given range
// May return null if level range is not compatible with the template
generateInLevelRange(level: IntegerRange, random = RandomGenerator.global): Equipment | null {
var random_range = this.getPowerRangeForLevel(level);
if (random_range) {
var power = random.random() * (random_range.max - random_range.min) + random_range.min;
return this.generateFixed(power);
} else {
return null;
}
});
}
/**
* Convenience function to add a modulated effect to the equipment
* Add a move action.
*/
addEffect(effect: BaseEffect, min_value: number, max_value: number | null = null, target = true) {
var template = new EffectTemplate(effect);
template.addModifier("value", new IntegerRange(min_value, max_value));
if (target) {
this.target_effects.push(template);
} else {
this.permanent_effects.push(template);
}
addMoveAction(distance_per_power: LeveledValue): void {
this.base_modifiers.push((equipment, level) => {
equipment.action = new MoveAction(equipment, resolveForLevel(distance_per_power, level));
});
}
/**
* Convenience function to add a modulated sticky effect to the equipment
* Add a fire weapon action.
*/
addStickyEffect(effect: BaseEffect, min_value: number, max_value: number | null = null, min_duration: number = 1,
max_duration: number | null = null, on_stick = false, on_turn_start = false, target = true): void {
var template = new EffectTemplate(new StickyEffect(effect, 0, on_stick, on_turn_start));
template.addModifier("value", new IntegerRange(min_value, max_value));
template.addModifier("duration", new IntegerRange(min_duration, max_duration));
if (target) {
this.target_effects.push(template);
} else {
this.permanent_effects.push(template);
}
addFireAction(power: LeveledValue, range: LeveledValue, blast: LeveledValue, effects: EffectTemplate<BaseEffect>[]): void {
this.base_modifiers.push((equipment, level) => {
let reffects = effects.map(effect => effect.generate(level));
equipment.action = new FireWeaponAction(equipment, resolveForLevel(power, level), resolveForLevel(range, level), resolveForLevel(blast, level), reffects);
});
}
/**
* Convenience function to add damage on target, immediate or over time
* Add a deploy drone action.
*/
addDamage(min_value: number, max_value: number | null = null, min_duration: number | null = null, max_duration: number | null = null) {
if (min_duration != null) {
this.addStickyEffect(new DamageEffect(), min_value, max_value, min_duration, max_duration, true, false, true);
} else {
this.addEffect(new DamageEffect(), min_value, max_value, true);
}
}
/**
* Convenience function to add an attribute on the ship that equips the loot
*/
increaseAttribute(attribute: keyof ShipAttributes, min_value: number, max_value: number | null = null) {
this.addEffect(new AttributeEffect(attribute), min_value, max_value, false);
}
/**
* Set the power consumption
*/
setPowerConsumption(minimal: number, maximal: number | null = null) {
this.ap_usage = new IntegerRange(minimal, maximal);
}
// Method to reimplement to assign an action to a generated equipment
protected getActionForEquipment(equipment: Equipment): BaseAction | null {
return null;
addDroneAction(power: LeveledValue, range: LeveledValue, lifetime: LeveledValue, radius: LeveledValue, effects: EffectTemplate<BaseEffect>[]): void {
this.base_modifiers.push((equipment, level) => {
let reffects = effects.map(effect => effect.generate(level));
equipment.action = new DeployDroneAction(equipment, resolveForLevel(power, level), resolveForLevel(range, level), resolveForLevel(lifetime, level), resolveForLevel(radius, level), reffects);
});
}
}
}

View file

@ -5,9 +5,7 @@ module TS.SpaceTac.Specs {
let ship = new Ship();
TestTools.setShipAP(ship, ship_ap);
TestTools.addEngine(ship, engine_distance);
let action = new FireWeaponAction(new Equipment(), true);
action.equipment.distance = distance;
action.equipment.ap_usage = weapon_ap;
let action = new FireWeaponAction(new Equipment(), weapon_ap, distance);
let simulator = new MoveFireSimulator(ship);
return [ship, simulator, action];
}
@ -23,7 +21,7 @@ module TS.SpaceTac.Specs {
let engine4 = TestTools.addEngine(ship, 70);
let best = simulator.findBestEngine();
expect(best).toBe(engine3);
expect((<Equipment>best).distance).toBe(150);
expect((<MoveAction>nn(best).action).distance_per_power).toBe(150);
});
it("fires directly when in range", function () {

View file

@ -51,7 +51,7 @@ module TS.SpaceTac {
if (engines.length == 0) {
return null;
} else {
engines.sort((a, b) => cmp(b.distance, a.distance));
engines.sort((a, b) => (a.action instanceof MoveAction && b.action instanceof MoveAction) ? cmp(b.action.distance_per_power, a.action.distance_per_power) : 0);
return engines[0];
}
}

View file

@ -47,13 +47,13 @@ module TS.SpaceTac.Specs {
slot = ship.addSlot(SlotType.Engine);
equipment = new Equipment();
equipment.slot = slot.type;
equipment.slot_type = slot.type;
equipment.action = new MoveAction(equipment);
slot.attach(equipment);
slot = ship.addSlot(SlotType.Weapon);
equipment = new Equipment();
equipment.slot = slot.type;
equipment.slot_type = slot.type;
slot.attach(equipment);
actions = ship.getAvailableActions();
@ -69,14 +69,14 @@ module TS.SpaceTac.Specs {
slot = ship.addSlot(SlotType.Power);
equipment = new Equipment();
equipment.slot = slot.type;
equipment.permanent_effects.push(new AttributeEffect("power_capacity", 4));
equipment.slot_type = slot.type;
equipment.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 AttributeEffect("power_capacity", 5));
equipment.slot_type = slot.type;
equipment.effects.push(new AttributeEffect("power_capacity", 5));
slot.attach(equipment);
ship.updateAttributes();
@ -246,9 +246,13 @@ module TS.SpaceTac.Specs {
it("recover action points at end of turn", function () {
var ship = new Ship();
var power_core_template = new Equipments.BasicPowerCore();
ship.addSlot(SlotType.Power).attach(power_core_template.generateFixed(0));
ship.updateAttributes();
let power_generator = new Equipment(SlotType.Power);
power_generator.effects = [
new AttributeEffect("power_capacity", 8),
new AttributeEffect("power_recovery", 3),
new AttributeEffect("power_initial", 4)
]
ship.addSlot(SlotType.Power).attach(power_generator);
expect(ship.values.power.get()).toBe(0);
ship.initializeActionPoints();

View file

@ -206,6 +206,10 @@ module TS.SpaceTac {
* Get a ship value
*/
getValue(name: keyof ShipValues): number {
if (!this.values.hasOwnProperty(name)) {
console.error(`No such ship value: ${name}`);
return 0;
}
return this.values[name].get();
}
@ -238,6 +242,10 @@ module TS.SpaceTac {
* Get a ship attribute's current value
*/
getAttribute(name: keyof ShipAttributes): number {
if (!this.attributes.hasOwnProperty(name)) {
console.error(`No such ship attribute: ${name}`);
return 0;
}
return this.attributes[name].get();
}
@ -496,7 +504,7 @@ module TS.SpaceTac {
* Returns true if successful
*/
equip(item: Equipment, from_cargo = true): boolean {
let free_slot = first(this.slots, slot => slot.type == item.slot && !slot.attached);
let free_slot = first(this.slots, slot => slot.type == item.slot_type && !slot.attached);
if (free_slot && (!from_cargo || remove(this.cargo, item))) {
free_slot.attach(item);
@ -627,7 +635,7 @@ module TS.SpaceTac {
this.slots.forEach(slot => {
if (slot.attached) {
slot.attached.permanent_effects.forEach(effect => {
slot.attached.effects.forEach(effect => {
if (effect.code == code) {
result.push(effect);
}

View file

@ -10,6 +10,7 @@ module TS.SpaceTac.Specs {
expect(ship.slots[0].type).toBe(SlotType.Shield);
expect(ship.slots[1].type).toBe(SlotType.Weapon);
expect(ship.slots[2].type).toBe(SlotType.Weapon);
expect(ship.getAttribute("skill_material")).toBe(1);
});
});
}

View file

@ -26,9 +26,12 @@ module TS.SpaceTac {
result.addSlot(slot);
});
// Set all skills to 1 (to be able to use at least basic equipment)
keys(result.skills).forEach(skill => result.upgradeSkill(skill));
// Fill equipment slots
result.slots.forEach((slot: Slot) => {
var equipment = loot.generate(new IntegerRange(level, level), slot.type);
var equipment = loot.generate(level, slot.type);
if (equipment) {
slot.attach(equipment)
if (slot.attached !== equipment) {

View file

@ -39,5 +39,24 @@ module TS.SpaceTac.Specs {
level.forceLevel(10);
expect(level.get()).toEqual(10);
});
it("computes needed level for given points", function () {
expect(ShipLevel.getLevelForPoints(0)).toBe(1);
expect(ShipLevel.getLevelForPoints(1)).toBe(1);
expect(ShipLevel.getLevelForPoints(2)).toBe(1);
expect(ShipLevel.getLevelForPoints(5)).toBe(1);
expect(ShipLevel.getLevelForPoints(10)).toBe(1);
expect(ShipLevel.getLevelForPoints(11)).toBe(2);
expect(ShipLevel.getLevelForPoints(15)).toBe(2);
expect(ShipLevel.getLevelForPoints(16)).toBe(3);
expect(ShipLevel.getLevelForPoints(526)).toBe(105);
});
it("computes needed points for given level", function () {
expect(ShipLevel.getPointsForLevel(1)).toBe(10);
expect(ShipLevel.getPointsForLevel(2)).toBe(15);
expect(ShipLevel.getPointsForLevel(3)).toBe(20);
expect(ShipLevel.getPointsForLevel(105)).toBe(530);
});
});
}

View file

@ -23,10 +23,24 @@ module TS.SpaceTac {
/**
* Force experience gain, to reach a given level
*/
forceLevel(level: number) {
forceLevel(level: number): void {
while (this.level < level) {
this.addExperience(this.getNextGoal());
this.checkLevelUp();
this.forceLevelUp();
}
}
/**
* Force a level up
*/
forceLevelUp(): void {
let old_level = this.level;
this.addExperience(this.getNextGoal() - this.experience);
this.checkLevelUp();
if (old_level >= this.level) {
// security against infinite loops
throw new Error("No effective level up");
}
}
@ -37,7 +51,7 @@ module TS.SpaceTac {
*/
checkLevelUp(): boolean {
let changed = false;
while (this.experience > this.getNextGoal()) {
while (this.experience >= this.getNextGoal()) {
this.level++;
changed = true;
}
@ -47,7 +61,7 @@ module TS.SpaceTac {
/**
* Add experience points
*/
addExperience(points: number) {
addExperience(points: number): void {
this.experience += points;
}
@ -57,5 +71,25 @@ module TS.SpaceTac {
getSkillPoints(): number {
return 5 + 5 * this.level;
}
/**
* Get the level needed to have a given total of points
*/
static getLevelForPoints(points: number): number {
let lev = new ShipLevel();
while (lev.getSkillPoints() < points) {
lev.forceLevelUp();
}
return lev.level;
}
/**
* Get the points available at a given level
*/
static getPointsForLevel(level: number): number {
let lev = new ShipLevel();
lev.forceLevel(level);
return lev.getSkillPoints();
}
}
}

View file

@ -10,8 +10,9 @@ module TS.SpaceTac {
* Generate a random stock
*/
generateStock(items: number) {
// TODO other levels
let generator = new LootGenerator();
this.stock = nna(range(items).map(i => generator.generate()));
this.stock = nna(range(items).map(i => generator.generate(1)));
this.sortStock();
}

View file

@ -5,13 +5,13 @@ module TS.SpaceTac.Specs {
var slot = ship.addSlot(SlotType.Engine);
var equipment = new Equipment();
equipment.slot = SlotType.Weapon;
equipment.slot_type = SlotType.Weapon;
expect(slot.attached).toBeNull();
slot.attach(equipment);
expect(slot.attached).toBeNull();
equipment.slot = SlotType.Engine;
equipment.slot_type = SlotType.Engine;
slot.attach(equipment);
expect(slot.attached).toBe(equipment);
@ -22,7 +22,7 @@ module TS.SpaceTac.Specs {
var slot = ship.addSlot(SlotType.Shield);
var equipment = new Equipment();
equipment.slot = SlotType.Shield;
equipment.slot_type = SlotType.Shield;
equipment.requirements["skill_gravity"] = 5;
expect(slot.attached).toBeNull();

View file

@ -28,7 +28,7 @@ module TS.SpaceTac {
// Attach an equipment in this slot
attach(equipment: Equipment): Equipment {
if (this.type === equipment.slot && equipment.canBeEquipped(this.ship)) {
if (this.type === equipment.slot_type && equipment.canBeEquipped(this.ship)) {
this.attached = equipment;
equipment.attached_to = this;

View file

@ -0,0 +1,35 @@
module TS.SpaceTac.Specs {
describe("TestTools", () => {
it("set ship power", () => {
let ship = new Ship();
expect(ship.getAttribute("power_capacity")).toBe(0);
expect(ship.getAttribute("power_initial")).toBe(0);
expect(ship.getAttribute("power_recovery")).toBe(0);
expect(ship.getValue("power")).toBe(0);
TestTools.setShipAP(ship, 12, 4);
expect(ship.getAttribute("power_capacity")).toBe(12);
expect(ship.getAttribute("power_initial")).toBe(12);
expect(ship.getAttribute("power_recovery")).toBe(4);
expect(ship.getValue("power")).toBe(12);
});
it("set ship health", () => {
let ship = new Ship();
expect(ship.getAttribute("hull_capacity")).toBe(0);
expect(ship.getAttribute("shield_capacity")).toBe(0);
expect(ship.getValue("hull")).toBe(0);
expect(ship.getValue("shield")).toBe(0);
TestTools.setShipHP(ship, 100, 200);
expect(ship.getAttribute("hull_capacity")).toBe(100);
expect(ship.getAttribute("shield_capacity")).toBe(200);
expect(ship.getValue("hull")).toBe(100);
expect(ship.getValue("shield")).toBe(200);
});
});
}

View file

@ -25,7 +25,8 @@ module TS.SpaceTac {
var equipped = ship.listEquipment(slot);
var equipment: Equipment;
if (force_generate || equipped.length === 0) {
equipment = template.generateFixed(0);
equipment = template.generate(1);
equipment.requirements = {};
ship.addSlot(slot).attach(equipment);
} else {
equipment = equipped[0];
@ -37,8 +38,7 @@ module TS.SpaceTac {
// Add an engine, allowing a ship to move *distance*, for each action points
static addEngine(ship: Ship, distance: number): Equipment {
var equipment = this.getOrGenEquipment(ship, SlotType.Engine, new Equipments.ConventionalEngine(), true);
equipment.ap_usage = 1;
equipment.distance = distance;
(<MoveAction>equipment.action).distance_per_power = distance;
return equipment;
}
@ -47,11 +47,7 @@ module TS.SpaceTac {
*/
static addWeapon(ship: Ship, damage = 100, power_usage = 1, max_distance = 100, blast = 0): Equipment {
var equipment = ship.addSlot(SlotType.Weapon).attach(new Equipment(SlotType.Weapon));
equipment.action = new FireWeaponAction(equipment, blast != 0, "Test Weapon");
equipment.ap_usage = power_usage;
equipment.blast = blast;
equipment.distance = max_distance;
equipment.target_effects.push(new DamageEffect(damage));
equipment.action = new FireWeaponAction(equipment, power_usage, max_distance, blast, [new DamageEffect(damage)], "Test Weapon");
return equipment;
}
@ -59,12 +55,14 @@ module TS.SpaceTac {
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 => {
equipment.effects.forEach(effect => {
if (effect instanceof AttributeEffect) {
if (effect.attrcode === "power_capacity") {
effect.value = points;
} else if (effect.attrcode === "power_recovery") {
effect.value = recovery;
} else if (effect.attrcode === "power_initial") {
effect.value = points;
}
}
});
@ -78,14 +76,14 @@ module TS.SpaceTac {
var armor = TestTools.getOrGenEquipment(ship, SlotType.Hull, new Equipments.IronHull());
var shield = TestTools.getOrGenEquipment(ship, SlotType.Shield, new Equipments.BasicForceField());
armor.permanent_effects.forEach(effect => {
armor.effects.forEach(effect => {
if (effect instanceof AttributeEffect) {
if (effect.attrcode === "hull_capacity") {
effect.value = hull_points;
}
}
});
shield.permanent_effects.forEach((effect: BaseEffect) => {
shield.effects.forEach((effect: BaseEffect) => {
if (effect instanceof AttributeEffect) {
if (effect.attrcode === "shield_capacity") {
effect.value = shield_points;

View file

@ -2,8 +2,8 @@ module TS.SpaceTac {
describe("BaseAction", function () {
it("check if equipment can be used with remaining AP", function () {
var equipment = new Equipment(SlotType.Hull);
equipment.ap_usage = 3;
var action = new BaseAction("test", "Test", false, equipment);
spyOn(action, "getActionPointsUsage").and.returnValue(3);
var ship = new Ship();
ship.addSlot(SlotType.Hull).attach(equipment);
ship.values.power.setMaximal(10);

View file

@ -39,7 +39,7 @@ module TS.SpaceTac {
if (remaining_ap === null) {
remaining_ap = ship.values.power.get();
}
var ap_usage = this.equipment ? this.equipment.ap_usage : 0;
var ap_usage = this.getActionPointsUsage(ship, null);
if (remaining_ap >= ap_usage) {
return null;
} else {
@ -49,29 +49,17 @@ module TS.SpaceTac {
// Get the number of action points the action applied to a target would use
getActionPointsUsage(ship: Ship, target: Target | null): number {
if (this.equipment) {
return this.equipment.ap_usage;
} else {
return 0;
}
return 0;
}
// Get the range of this action
getRangeRadius(ship: Ship): number {
if (this.equipment) {
return this.equipment.distance;
} else {
return 0;
}
return 0;
}
// Get the effect area radius of this action
getBlastRadius(ship: Ship): number {
if (this.equipment) {
return this.equipment.blast;
} else {
return 0;
}
return 0;
}
// Method to check if a target is applicable for this action
@ -129,5 +117,12 @@ module TS.SpaceTac {
// Method to reimplement to apply a action
protected customApply(ship: Ship, target: Target | null) {
}
/**
* Get description of effects (one line per effect)
*/
getEffectsDescription(): string[] {
return [];
}
}
}

View file

@ -15,10 +15,7 @@ module TS.SpaceTac {
it("allows to deploy in range", function () {
let ship = new Ship();
ship.setArenaPosition(0, 0);
let equipment = new Equipment();
equipment.distance = 8;
equipment.ap_usage = 0;
let action = new DeployDroneAction(equipment);
let action = new DeployDroneAction(new Equipment(), 0, 8);
expect(action.checkTarget(ship, new Target(8, 0, null))).toEqual(new Target(8, 0, null));
expect(action.checkTarget(ship, new Target(12, 0, null))).toEqual(new Target(8, 0, null));
@ -34,14 +31,8 @@ module TS.SpaceTac {
ship.setArenaPosition(0, 0);
battle.playing_ship = ship;
TestTools.setShipAP(ship, 3);
let equipment = new Equipment();
equipment.code = "testdrone";
equipment.distance = 8;
equipment.ap_usage = 2;
equipment.duration = 2;
equipment.blast = 4;
equipment.target_effects.push(new DamageEffect(50));
let action = new DeployDroneAction(equipment);
let equipment = new Equipment(SlotType.Weapon, "testdrone");
let action = new DeployDroneAction(equipment, 2, 8, 2, 4, [new DamageEffect(50)]);
battle.log.clear();
battle.log.addFilter("value");

View file

@ -5,15 +5,49 @@ module TS.SpaceTac {
* Action to deploy a drone in space
*/
export class DeployDroneAction extends BaseAction {
// Power usage
power: number;
// Maximal distance the drone may be deployed
deploy_distance: number;
// Effect radius of the deployed drone
effect_radius: number;
// Duration of the drone in turns, before being destroyed
lifetime: number;
// Effects applied to ships in range of the drone
effects: BaseEffect[];
// Source equipment
equipment: Equipment;
constructor(equipment: Equipment) {
constructor(equipment: Equipment, power = 1, deploy_distance = 0, lifetime = 0, effect_radius = 0, effects: BaseEffect[] = []) {
super("deploy-" + equipment.code, "Deploy", true, equipment);
this.power = power;
this.deploy_distance = deploy_distance;
this.lifetime = lifetime;
this.effect_radius = effect_radius;
this.effects = effects;
}
getActionPointsUsage(ship: Ship, target: Target | null): number {
return this.power;
}
getRangeRadius(ship: Ship): number {
return this.deploy_distance;
}
getBlastRadius(ship: Ship): number {
return this.effect_radius;
}
checkLocationTarget(ship: Ship, target: Target): Target {
// TODO Not too close to other ships and drones
target = target.constraintInRange(ship.arena_x, ship.arena_y, this.equipment.distance);
target = target.constraintInRange(ship.arena_x, ship.arena_y, this.deploy_distance);
return target;
}
@ -21,14 +55,19 @@ module TS.SpaceTac {
let drone = new Drone(ship, this.equipment.code);
drone.x = target.x;
drone.y = target.y;
drone.radius = this.equipment.blast;
drone.effects = this.equipment.target_effects;
drone.duration = this.equipment.duration;
drone.radius = this.effect_radius;
drone.effects = this.effects;
drone.duration = this.lifetime;
let battle = ship.getBattle();
if (battle) {
battle.addDrone(drone);
}
}
getEffectsDescription(): string[] {
let desc = `drone for ${this.lifetime} turn${this.lifetime > 1 ? "s" : ""}, in ${this.deploy_distance}km range, with ${this.effect_radius}km radius effects`;
return [desc].concat(this.effects.map(effect => effect.getDescription()));
}
}
}

View file

@ -16,13 +16,9 @@ module TS.SpaceTac {
let fleet = new Fleet();
let ship = new Ship(fleet);
let equipment = new Equipment(SlotType.Weapon, "testweapon");
equipment.blast = 10;
equipment.ap_usage = 5;
equipment.distance = 100;
let effect = new BaseEffect("testeffect");
let mock_apply = spyOn(effect, "applyOnShip").and.stub();
equipment.target_effects.push(effect);
let action = new FireWeaponAction(equipment, true);
let action = new FireWeaponAction(equipment, 5, 100, 10, [effect]);
TestTools.setShipAP(ship, 10);

View file

@ -1,23 +1,49 @@
/// <reference path="BaseAction.ts"/>
module TS.SpaceTac {
// Action to fire a weapon on another ship, or in space
/**
* Action to fire a weapon on another ship, or in space
*/
export class FireWeaponAction extends BaseAction {
// Boolean set to true if the weapon can target space
can_target_space: boolean;
// Power consumption
power: number;
// Maximal range of the weapon
range: number
// Blast radius
blast: number;
// Effects applied on hit
effects: BaseEffect[];
// Equipment cannot be null
equipment: Equipment;
constructor(equipment: Equipment, can_target_space = false, name = "Fire") {
constructor(equipment: Equipment, power = 1, range = 0, blast = 0, effects: BaseEffect[] = [], name = "Fire") {
super("fire-" + equipment.code, name, true, equipment);
this.can_target_space = can_target_space;
this.power = power;
this.range = range;
this.effects = effects;
this.blast = blast;
}
getActionPointsUsage(ship: Ship, target: Target | null): number {
return this.power;
}
getRangeRadius(ship: Ship): number {
return this.range;
}
getBlastRadius(ship: Ship): number {
return this.blast;
}
checkLocationTarget(ship: Ship, target: Target): Target | null {
if (target && this.can_target_space) {
target = target.constraintInRange(ship.arena_x, ship.arena_y, this.equipment.distance);
if (target && this.blast > 0) {
target = target.constraintInRange(ship.arena_x, ship.arena_y, this.range);
return target;
} else {
return null;
@ -30,9 +56,9 @@ module TS.SpaceTac {
return null;
} else {
// Check if target is in range
if (this.can_target_space) {
if (this.blast > 0) {
return this.checkLocationTarget(ship, new Target(target.x, target.y));
} else if (target.isInRange(ship.arena_x, ship.arena_y, this.equipment.distance)) {
} else if (target.isInRange(ship.arena_x, ship.arena_y, this.range)) {
return target;
} else {
return null;
@ -49,7 +75,7 @@ module TS.SpaceTac {
let battle = ship.getBattle();
let ships = (blast && battle) ? battle.collectShipsInCircle(target, blast, true) : ((target.ship && target.ship.alive) ? [target.ship] : []);
ships.forEach(ship => {
this.equipment.target_effects.forEach(effect => result.push([ship, effect]));
this.effects.forEach(effect => result.push([ship, effect]));
});
return result;
}
@ -65,5 +91,15 @@ module TS.SpaceTac {
let effects = this.getEffects(ship, target);
effects.forEach(([ship, effect]) => effect.applyOnShip(ship));
}
getEffectsDescription(): string[] {
return this.effects.map(effect => {
let suffix = this.blast ? `in ${this.blast}km radius` : "on target";
if (effect instanceof StickyEffect) {
suffix = `for ${effect.duration} turn${effect.duration > 1 ? "s" : ""} ${suffix}`;
}
return effect.getDescription() + " " + suffix;
});
}
}
}

View file

@ -9,9 +9,7 @@ module TS.SpaceTac {
ship.arena_x = 0;
ship.arena_y = 0;
var engine = new Equipment();
engine.distance = 1;
engine.ap_usage = 2;
var action = new MoveAction(engine);
var action = new MoveAction(engine, 0.5);
expect(action.getDistanceByActionPoint(ship)).toBe(0.5);
@ -46,9 +44,7 @@ module TS.SpaceTac {
ship.arena_x = 0;
ship.arena_y = 0;
var engine = new Equipment();
engine.distance = 1;
engine.ap_usage = 1;
var action = new MoveAction(engine);
var action = new MoveAction(engine, 1);
battle.playing_ship = ship;
spyOn(console, "warn").and.stub();
@ -84,12 +80,12 @@ module TS.SpaceTac {
var battle = TestTools.createBattle(1, 1);
var ship = battle.fleets[0].ships[0];
var enemy = battle.fleets[1].ships[0];
var engine = TestTools.addEngine(ship, 10);
TestTools.setShipAP(ship, 100);
ship.setArenaPosition(5, 5);
enemy.setArenaPosition(10, 5);
var action = new MoveAction(engine);
var action = new MoveAction(new Equipment());
action.distance_per_power = 10;
action.safety_distance = 2;
var result = action.checkLocationTarget(ship, Target.newFromLocation(7, 5));

View file

@ -1,6 +1,8 @@
module TS.SpaceTac {
// Action to move to a given location
export class MoveAction extends BaseAction {
// Distance allowed for each power point
distance_per_power: number;
// Safety distance from other ships
safety_distance: number;
@ -8,9 +10,10 @@ module TS.SpaceTac {
// Equipment cannot be null (engine)
equipment: Equipment;
constructor(equipment: Equipment) {
constructor(equipment: Equipment, distance_per_power = 0) {
super("move", "Move", true, equipment);
this.distance_per_power = distance_per_power;
this.safety_distance = 50;
}
@ -37,18 +40,18 @@ module TS.SpaceTac {
}
var distance = Target.newFromShip(ship).getDistanceTo(target);
return Math.ceil(this.equipment.ap_usage * distance / this.equipment.distance);
return Math.ceil(distance / this.distance_per_power);
}
getRangeRadius(ship: Ship): number {
return ship.values.power.get() * this.equipment.distance / this.equipment.ap_usage;
return ship.values.power.get() * this.distance_per_power;
}
/**
* Get the distance that may be traveled with 1 action point
*/
getDistanceByActionPoint(ship: Ship): number {
return this.equipment.distance / this.equipment.ap_usage;
return this.distance_per_power;
}
checkLocationTarget(ship: Ship, target: Target): Target {

View file

@ -25,10 +25,10 @@ module TS.SpaceTac.Specs {
expect(result.length).toBe(0);
var weapon1 = new Equipment(SlotType.Weapon, "weapon1");
weapon1.target_effects.push(new DamageEffect(50));
weapon1.action = new FireWeaponAction(weapon1, 1, 1, 1, [new DamageEffect(50)]);
ai.ship.addSlot(SlotType.Weapon).attach(weapon1);
var weapon2 = new Equipment(SlotType.Weapon, "weapon2");
weapon2.target_effects.push(new DamageEffect(100));
weapon2.action = new FireWeaponAction(weapon1, 1, 1, 1, [new DamageEffect(100)]);
ai.ship.addSlot(SlotType.Weapon).attach(weapon2);
var weapon3 = new Equipment(SlotType.Weapon, "weapon3");
ai.ship.addSlot(SlotType.Weapon).attach(weapon3);
@ -207,16 +207,11 @@ module TS.SpaceTac.Specs {
ai.move_margin = 0;
var engine = new Equipment(SlotType.Engine);
engine.distance = 1;
engine.ap_usage = 2;
engine.action = new MoveAction(engine);
engine.action = new MoveAction(engine, 0.5);
ai.ship.addSlot(SlotType.Engine).attach(engine);
var weapon = new Equipment(SlotType.Weapon);
weapon.distance = 6;
weapon.ap_usage = 1;
weapon.target_effects.push(new DamageEffect(20));
weapon.action = new FireWeaponAction(weapon);
weapon.action = new FireWeaponAction(weapon, 1, 6, 0, [new DamageEffect(20)]);
ai.ship.addSlot(SlotType.Weapon).attach(weapon);
ai.ship.values.power.setMaximal(10);

View file

@ -53,7 +53,7 @@ module TS.SpaceTac {
// List all weapons
listAllWeapons(): Equipment[] {
return this.ship.listEquipment(SlotType.Weapon).filter(equipement => any(equipement.target_effects, effect => effect instanceof DamageEffect));
return this.ship.listEquipment(SlotType.Weapon).filter(equipement => equipement.action instanceof FireWeaponAction && any(equipement.action.effects, effect => effect instanceof DamageEffect));
}
// List all available maneuvers for the playing ship

View file

@ -38,10 +38,10 @@ module TS.SpaceTac {
*/
static produceBlastShots(ship: Ship, battle: Battle): TacticalProducer {
// TODO Work with groups of 3, 4 ...
let weapons = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.blast > 0);
let weapons = ifilter(iarray(ship.listEquipment(SlotType.Weapon)), weapon => weapon.action instanceof FireWeaponAction && weapon.action.blast > 0);
let enemies = battle.ienemies(ship.getPlayer());
let couples = ifilter(icombine(enemies, enemies), ([e1, e2]) => e1 != e2);
let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.blast * 2);
let candidates = ifilter(icombine(weapons, couples), ([weapon, [e1, e2]]) => Target.newFromShip(e1).getDistanceTo(Target.newFromShip(e2)) < weapon.action.getBlastRadius(ship) * 2);
let result = imap(candidates, ([weapon, [e1, e2]]) => new Maneuver(ship, weapon, Target.newFromLocation((e1.arena_x + e2.arena_x) / 2, (e1.arena_y + e2.arena_y) / 2)));
return result;
}

View file

@ -10,17 +10,6 @@ module TS.SpaceTac {
this.code = code;
}
/**
* Get a copy, modified by template modifiers
*/
getModifiedCopy(modifiers: EffectTemplateModifier[], power: number): BaseEffect {
let result = copy(this);
modifiers.forEach(modifier => {
result[modifier.name] = modifier.range.getProportional(power);
});
return result;
}
// Apply ponctually the effect on a given ship
// Return true if the effect could be applied
applyOnShip(ship: Ship): boolean {

View file

@ -29,13 +29,6 @@ module TS.SpaceTac {
this.on_turn_end = on_turn_end;
}
getModifiedCopy(modifiers: EffectTemplateModifier[], power: number): BaseEffect {
let [current, base] = binpartition(modifiers, modifier => modifier.name == "duration");
let result = <StickyEffect>super.getModifiedCopy(current, power);
result.base = result.base.getModifiedCopy(base, power);
return result;
}
applyOnShip(ship: Ship): boolean {
ship.addStickyEffect(new StickyEffect(this.base, this.duration, this.on_stick, this.on_turn_end));
if (this.on_stick) {

View file

@ -1,48 +0,0 @@
module TS.SpaceTac.Equipments {
describe("AbstractDrone", function () {
it("can be configured", function () {
let template = new AbstractDrone("test");
expect(template.name).toEqual("test");
template.setDeployDistance(5, 8);
expect(template.distance).toEqual(new Range(5, 8));
template.setEffectRadius(100, 300);
expect(template.blast).toEqual(new IntegerRange(100, 300));
template.setLifetime(2, 3);
expect(template.duration).toEqual(new IntegerRange(2, 3));
});
it("generates a drone-deploying equipment", function () {
let template = new AbstractDrone("Test");
template.setDeployDistance(100, 200);
template.setEffectRadius(50, 100);
template.setLifetime(2, 3);
template.addDamage(20, 30);
template.setPowerConsumption(3, 5);
let equipment = template.generateFixed(0);
expect(equipment.action).toEqual(new DeployDroneAction(equipment));
expect(equipment.ap_usage).toEqual(3);
expect(equipment.blast).toEqual(50);
expect(equipment.code).toEqual("test");
expect(equipment.distance).toEqual(100);
expect(equipment.duration).toEqual(2);
expect(equipment.name).toEqual("Test");
expect(equipment.permanent_effects).toEqual([]);
expect(equipment.slot).toEqual(SlotType.Weapon);
expect(equipment.target_effects).toEqual([new DamageEffect(20)]);
equipment = template.generateFixed(1);
expect(equipment.action).toEqual(new DeployDroneAction(equipment));
expect(equipment.ap_usage).toEqual(5);
expect(equipment.blast).toEqual(100);
expect(equipment.code).toEqual("test");
expect(equipment.distance).toEqual(200);
expect(equipment.duration).toEqual(3);
expect(equipment.name).toEqual("Test");
expect(equipment.permanent_effects).toEqual([]);
expect(equipment.slot).toEqual(SlotType.Weapon);
expect(equipment.target_effects).toEqual([new DamageEffect(30)]);
});
});
}

View file

@ -1,40 +0,0 @@
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
/**
* Base class for all weapon equipment that deploys a drone.
*/
export class AbstractDrone extends LootTemplate {
constructor(name: string) {
super(SlotType.Weapon, name);
}
/**
* Set the maximal distance at which the drone may be deployed
*
* Be aware that *min_distance* means the MAXIMAL reachable distance, but on a low-power loot !
*/
setDeployDistance(min_distance: number, max_distance: number | null = null): void {
this.distance = new Range(min_distance, max_distance);
}
/**
* Set the effect radius of the deployed drone
*/
setEffectRadius(min_radius: number, max_radius: number | null = null): void {
this.blast = new IntegerRange(min_radius, max_radius);
}
/**
* Set the drone lifetime
*/
setLifetime(min_lifetime: number, max_lifetime: number | null = null): void {
this.duration = new IntegerRange(min_lifetime, max_lifetime);
}
protected getActionForEquipment(equipment: Equipment): BaseAction {
var result = new DeployDroneAction(equipment);
return result;
}
}
}

View file

@ -1,139 +0,0 @@
module TS.SpaceTac.Specs {
describe("AbstractWeapon", function () {
it("has fire action, and damage effects on target", function () {
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 50, 60);
var equipment = weapon.generateFixed(0.1);
expect(equipment.target_effects.length).toBe(1);
var effect = <DamageEffect>equipment.target_effects[0];
expect(effect.code).toEqual("damage");
expect(effect.value).toEqual(51);
var action = equipment.action;
expect(action.code).toEqual("fire-superfireweapon");
expect(action.needs_target).toBe(true);
});
it("controls ability to target emptiness", function () {
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 50, 60);
weapon.setRange(10, 20, true);
var equipment = weapon.generateFixed(20);
var action = <FireWeaponAction>equipment.action;
expect(action.can_target_space).toBe(true);
weapon.setRange(10, 20, false);
equipment = weapon.generateFixed(20);
action = <FireWeaponAction>equipment.action;
expect(action.can_target_space).toBe(false);
});
it("can't fire without sufficient AP", function () {
var ship = new Ship();
ship.values.power.set(3);
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 50);
weapon.ap_usage = new Range(2);
var equipment = weapon.generateFixed(0);
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
weapon.ap_usage = new Range(3);
equipment = weapon.generateFixed(0);
expect(equipment.action.checkCannotBeApplied(ship)).toBe(null);
weapon.ap_usage = new Range(4);
equipment = weapon.generateFixed(0);
expect(equipment.action.checkCannotBeApplied(ship)).toBe("not enough power");
});
it("can't friendly fire", function () {
var fleet1 = new Fleet(new Player());
var fleet2 = new Fleet(new Player());
var ship1a = new Ship(fleet1);
var ship1b = new Ship(fleet1);
var ship2a = new Ship(fleet2);
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 50, 60);
weapon.setRange(10, 10);
var equipment = weapon.generateFixed(0);
expect(equipment.action.checkShipTarget(ship1a, Target.newFromShip(ship2a))).toEqual(
Target.newFromShip(ship2a));
expect(equipment.action.checkShipTarget(ship1a, Target.newFromShip(ship1b))).toBeNull();
});
it("can't fire farther than its range", function () {
var fleet1 = new Fleet(new Player());
var fleet2 = new Fleet(new Player());
var ship = new Ship(fleet1);
ship.setArenaPosition(10, 10);
var weapon = new Equipments.AbstractWeapon("Super Fire Weapon", 50);
weapon.setRange(10, 10, true);
var equipment = weapon.generateFixed(0);
expect(equipment.distance).toEqual(10);
expect(equipment.action.checkLocationTarget(ship, Target.newFromLocation(15, 10))).toEqual(
Target.newFromLocation(15, 10));
expect(equipment.action.checkLocationTarget(ship, Target.newFromLocation(30, 10))).toEqual(
Target.newFromLocation(20, 10));
// Ship targetting
var ship2 = new Ship(fleet2);
ship2.setArenaPosition(10, 15);
expect(equipment.action.checkShipTarget(ship, Target.newFromShip(ship2))).toEqual(
Target.newFromLocation(10, 15));
ship2.setArenaPosition(10, 25);
expect(equipment.action.checkShipTarget(ship, Target.newFromShip(ship2))).toEqual(
Target.newFromLocation(10, 20));
// Forbid targetting in space
weapon.setRange(10, 10, false);
equipment = weapon.generateFixed(0);
expect(equipment.action.checkLocationTarget(ship, Target.newFromLocation(15, 10))).toBeNull();
});
it("can target an enemy ship and damage it", function () {
var fleet1 = new Fleet(new Player());
var fleet2 = new Fleet(new Player());
var ship1 = new Ship(fleet1);
ship1.values.power.set(50);
var ship2 = new Ship(fleet2);
ship2.setAttribute("hull_capacity", 100);
ship2.setAttribute("shield_capacity", 30);
ship2.restoreHealth();
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);
var equipment = weapon.generateFixed(0);
equipment.action.apply(ship1, Target.newFromShip(ship2));
expect(ship2.values.hull.get()).toEqual(100);
expect(ship2.values.shield.get()).toEqual(10);
expect(ship1.values.power.get()).toEqual(49);
equipment.action.apply(ship1, Target.newFromShip(ship2));
expect(ship2.values.hull.get()).toEqual(90);
expect(ship2.values.shield.get()).toEqual(0);
expect(ship1.values.power.get()).toEqual(48);
equipment.action.apply(ship1, Target.newFromShip(ship2));
expect(ship2.values.hull.get()).toEqual(70);
expect(ship2.values.shield.get()).toEqual(0);
expect(ship1.values.power.get()).toEqual(47);
});
});
}

View file

@ -1,36 +0,0 @@
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
// Base convenience class for weapons
export class AbstractWeapon extends LootTemplate {
// Boolean set to true if the weapon can target space
can_target_space: boolean;
constructor(name: string, min_damage: number = 0, max_damage: number | null = null) {
super(SlotType.Weapon, name);
this.can_target_space = false;
if (min_damage > 0 || (max_damage != null && max_damage > 0)) {
this.addDamage(min_damage, max_damage);
}
}
// Set the range for this weapon
// Pay attention that *min_distance* means the MAXIMAL reachable distance, but on a low-power loot
setRange(min_distance: number, max_distance: number | null = null, can_target_space = false): void {
this.distance = new Range(min_distance, max_distance);
this.can_target_space = can_target_space;
}
// Set the effect radius (blast) for this weapon
setBlast(min_blast: number, max_blast: number | null = null): void {
this.blast = new IntegerRange(min_blast, max_blast);
}
protected getActionForEquipment(equipment: Equipment): BaseAction {
var result = new FireWeaponAction(equipment, this.can_target_space);
return result;
}
}
}

View file

@ -0,0 +1,23 @@
module TS.SpaceTac.Equipments {
describe("BasicForceField", function () {
it("generates equipment based on level", function () {
let template = new BasicForceField();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_energy": 1 });
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 100)]);
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_energy": 2 });
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 150)]);
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_energy": 3 });
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 200)]);
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_energy": 10 });
expect(equipment.effects).toEqual([new AttributeEffect("shield_capacity", 550)]);
});
});
}

View file

@ -5,9 +5,8 @@ module TS.SpaceTac.Equipments {
constructor() {
super(SlotType.Shield, "Basic Force Field");
this.min_level = new IntegerRange(1, 3);
this.increaseAttribute("shield_capacity", 100, 200);
this.setSkillsRequirements({ "skill_energy": 1 });
this.addAttributeEffect("shield_capacity", istep(100, irepeat(50)));
}
}
}

View file

@ -0,0 +1,43 @@
module TS.SpaceTac.Equipments {
describe("BasicPowerCore", function () {
it("generates equipment based on level", function () {
let template = new BasicPowerCore();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_energy": 1 });
expect(equipment.effects).toEqual([
new AttributeEffect("initiative", 1),
new AttributeEffect("power_capacity", 6),
new AttributeEffect("power_initial", 4),
new AttributeEffect("power_recovery", 3),
]);
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_energy": 2 });
expect(equipment.effects).toEqual([
new AttributeEffect("initiative", 2),
new AttributeEffect("power_capacity", 7),
new AttributeEffect("power_initial", 4),
new AttributeEffect("power_recovery", 3),
]);
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_energy": 3 });
expect(equipment.effects).toEqual([
new AttributeEffect("initiative", 3),
new AttributeEffect("power_capacity", 8),
new AttributeEffect("power_initial", 5),
new AttributeEffect("power_recovery", 3),
]);
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_energy": 10 });
expect(equipment.effects).toEqual([
new AttributeEffect("initiative", 10),
new AttributeEffect("power_capacity", 15),
new AttributeEffect("power_initial", 8),
new AttributeEffect("power_recovery", 5),
]);
});
});
}

View file

@ -5,12 +5,11 @@ module TS.SpaceTac.Equipments {
constructor() {
super(SlotType.Power, "Basic Power Core");
this.min_level = new IntegerRange(1, 1);
this.increaseAttribute("initiative", 1);
this.increaseAttribute("power_capacity", 8);
this.increaseAttribute("power_initial", 4);
this.increaseAttribute("power_recovery", 3);
this.setSkillsRequirements({ "skill_energy": 1 });
this.addAttributeEffect("initiative", istep(1));
this.addAttributeEffect("power_capacity", istep(6));
this.addAttributeEffect("power_initial", istep(4, irepeat(0.5)));
this.addAttributeEffect("power_recovery", istep(3, irepeat(0.3)));
}
}
}

View file

@ -0,0 +1,27 @@
module TS.SpaceTac.Equipments {
describe("ConventionalEngine", function () {
it("generates equipment based on level", function () {
let template = new ConventionalEngine();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_energy": 1 });
expect(equipment.effects).toEqual([new AttributeEffect("initiative", 1)]);
expect(equipment.action).toEqual(new MoveAction(equipment, 200));
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_energy": 2 });
expect(equipment.effects).toEqual([new AttributeEffect("initiative", 2)]);
expect(equipment.action).toEqual(new MoveAction(equipment, 220));
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_energy": 3 });
expect(equipment.effects).toEqual([new AttributeEffect("initiative", 3)]);
expect(equipment.action).toEqual(new MoveAction(equipment, 240));
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_energy": 10 });
expect(equipment.effects).toEqual([new AttributeEffect("initiative", 10)]);
expect(equipment.action).toEqual(new MoveAction(equipment, 380));
});
});
}

View file

@ -6,15 +6,9 @@ module TS.SpaceTac.Equipments {
constructor() {
super(SlotType.Engine, "Conventional Engine");
this.min_level = new IntegerRange(1, 1);
this.distance = new Range(200, 200);
this.ap_usage = new IntegerRange(1);
this.increaseAttribute("initiative", 1);
}
protected getActionForEquipment(equipment: Equipment): BaseAction {
return new MoveAction(equipment);
this.setSkillsRequirements({ "skill_energy": 1 });
this.addAttributeEffect("initiative", 1);
this.addMoveAction(istep(200, irepeat(20)));
}
}
}

View file

@ -0,0 +1,23 @@
module TS.SpaceTac.Equipments {
describe("GatlingGun", function () {
it("generates equipment based on level", function () {
let template = new GatlingGun();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_material": 1 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 600, 0, [new DamageEffect(50)]));
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_material": 2 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 600, 0, [new DamageEffect(60)]));
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_material": 3 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 600, 0, [new DamageEffect(70)]));
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_material": 10 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 3, 600, 0, [new DamageEffect(140)]));
});
});
}

View file

@ -1,14 +1,14 @@
/// <reference path="AbstractWeapon.ts"/>
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
export class GatlingGun extends AbstractWeapon {
export class GatlingGun extends LootTemplate {
constructor() {
super("Gatling Gun", 50, 100);
super(SlotType.Weapon, "Gatling Gun");
this.setRange(600, 600, false);
this.ap_usage = new IntegerRange(3, 4);
this.min_level = new IntegerRange(1, 3);
this.setSkillsRequirements({ "skill_material": 1 });
this.addFireAction(irepeat(3), irepeat(600), 0, [
new EffectTemplate(new DamageEffect(), { "value": istep(50, irepeat(10)) })
]);
}
}
}

View file

@ -0,0 +1,23 @@
module TS.SpaceTac.Equipments {
describe("IronHull", function () {
it("generates equipment based on level", function () {
let template = new IronHull();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_material": 1 });
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 200)]);
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_material": 2 });
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 250)]);
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_material": 3 });
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 300)]);
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_material": 10 });
expect(equipment.effects).toEqual([new AttributeEffect("hull_capacity", 650)]);
});
});
}

View file

@ -5,9 +5,8 @@ module TS.SpaceTac.Equipments {
constructor() {
super(SlotType.Hull, "Iron Hull");
this.min_level = new IntegerRange(1, 3);
this.increaseAttribute("hull_capacity", 200, 300);
this.setSkillsRequirements({ "skill_material": 1 });
this.addAttributeEffect("hull_capacity", istep(200, irepeat(50)));
}
}
}

View file

@ -1,8 +1,28 @@
module TS.SpaceTac.Specs {
module TS.SpaceTac.Equipments {
describe("PowerDepleter", () => {
it("generates equipment based on level", function () {
let template = new PowerDepleter();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_energy": 1 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 500, 0, [new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true)]));
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_energy": 2 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 520, 0, [new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true)]));
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_energy": 3 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 540, 0, [new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true)]));
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_energy": 10 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 680, 0, [new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true)]));
});
it("limits target's AP", () => {
var template = new Equipments.PowerDepleter();
var equipment = template.generateFixed(0);
var template = new PowerDepleter();
var equipment = template.generate(1);
var ship = new Ship();
var target = new Ship();
@ -15,9 +35,9 @@ module TS.SpaceTac.Specs {
// Attribute is immediately limited
equipment.action.apply(ship, Target.newFromShip(target));
expect(target.values.power.get()).toBe(4);
expect(target.values.power.get()).toBe(3);
expect(target.sticky_effects).toEqual([
new StickyEffect(new AttributeLimitEffect("power_capacity", 4), 2, true, false)
new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 2, true, false)
]);
// Attribute is limited for two turns, and prevents AP recovery
@ -25,14 +45,14 @@ module TS.SpaceTac.Specs {
target.recoverActionPoints();
target.startTurn();
expect(target.values.power.get()).toBe(4);
expect(target.values.power.get()).toBe(3);
expect(target.sticky_effects).toEqual([
new StickyEffect(new AttributeLimitEffect("power_capacity", 4), 1, true, false)
new StickyEffect(new AttributeLimitEffect("power_capacity", 3), 1, true, false)
]);
target.endTurn();
target.recoverActionPoints();
expect(target.values.power.get()).toBe(4);
expect(target.values.power.get()).toBe(3);
target.startTurn();
expect(target.sticky_effects).toEqual([]);
@ -40,7 +60,7 @@ module TS.SpaceTac.Specs {
// Effect vanished, so AP recovery happens
target.endTurn();
expect(target.values.power.get()).toBe(6);
expect(target.values.power.get()).toBe(5);
expect(target.sticky_effects).toEqual([]);
});
});

View file

@ -1,16 +1,14 @@
/// <reference path="AbstractWeapon.ts"/>
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
export class PowerDepleter extends AbstractWeapon {
export class PowerDepleter extends LootTemplate {
constructor() {
super("Power Depleter");
super(SlotType.Weapon, "Power Depleter");
this.setRange(500, 700, false);
this.ap_usage = new IntegerRange(4, 5);
this.min_level = new IntegerRange(1, 3);
this.addStickyEffect(new AttributeLimitEffect("power_capacity"), 4, 3, 2, 3, true);
this.setSkillsRequirements({ "skill_energy": 1 });
this.addFireAction(irepeat(4), istep(500, irepeat(20)), 0, [
new StickyEffectTemplate(new AttributeLimitEffect("power_capacity"), { "value": irepeat(3) }, irepeat(2))
]);
}
}
}

View file

@ -1,10 +1,30 @@
module TS.SpaceTac.Equipments {
describe("RepairDrone", function () {
it("generates equipment based on level", function () {
let template = new RepairDrone();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_human": 1 });
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 300, 1, 100, [new ValueEffect("hull", 30)]));
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_human": 2 });
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 310, 1, 110, [new ValueEffect("hull", 33)]));
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_human": 3 });
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 320, 1, 120, [new ValueEffect("hull", 36)]));
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_human": 10 });
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 390, 2, 190, [new ValueEffect("hull", 57)]));
});
it("generates a drone that may repair ships hull", function () {
let template = new RepairDrone();
let equipment = template.generateFixed(0);
expect(equipment.target_effects).toEqual([new ValueEffect("hull", 30)]);
let equipment = template.generate(1);
expect(equipment.action).toEqual(new DeployDroneAction(equipment, 4, 300, 1, 100, [new ValueEffect("hull", 30)]));
let battle = new Battle();
let ship = battle.fleets[0].addShip();

View file

@ -1,21 +1,17 @@
/// <reference path="AbstractDrone.ts"/>
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
/**
* Drone that repairs damage done to the hull.
*/
export class RepairDrone extends AbstractDrone {
export class RepairDrone extends LootTemplate {
constructor() {
super("Repair Drone");
super(SlotType.Weapon, "Repair Drone");
this.min_level = new IntegerRange(1, 4);
this.setLifetime(1, 2);
this.setDeployDistance(300, 400);
this.setEffectRadius(100, 200);
this.setPowerConsumption(4, 5);
this.addEffect(new ValueEffect("hull"), 30, 60);
this.setSkillsRequirements({ "skill_human": 1 });
this.addDroneAction(irepeat(4), istep(300, irepeat(10)), istep(1, irepeat(0.2)), istep(100, irepeat(10)), [
new EffectTemplate(new ValueEffect("hull"), { "value": istep(30, irepeat(3)) })
]);
}
}
}

View file

@ -1,6 +1,26 @@
module TS.SpaceTac.Specs {
describe("SubMunitionMissile", () => {
it("hits several targets in circle", () => {
module TS.SpaceTac.Equipments {
describe("SubMunitionMissile", function () {
it("generates equipment based on level", function () {
let template = new SubMunitionMissile();
let equipment = template.generate(1);
expect(equipment.requirements).toEqual({ "skill_material": 1 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 500, 150, [new DamageEffect(30)]));
equipment = template.generate(2);
expect(equipment.requirements).toEqual({ "skill_material": 2 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 520, 155, [new DamageEffect(32)]));
equipment = template.generate(3);
expect(equipment.requirements).toEqual({ "skill_material": 3 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 540, 160, [new DamageEffect(34)]));
equipment = template.generate(10);
expect(equipment.requirements).toEqual({ "skill_material": 10 });
expect(equipment.action).toEqual(new FireWeaponAction(equipment, 4, 680, 195, [new DamageEffect(48)]));
});
it("hits several targets in circle", function () {
var battle = TestTools.createBattle(1, 2);
var ship = battle.fleets[0].ships[0];
@ -15,10 +35,11 @@ module TS.SpaceTac.Specs {
TestTools.setShipHP(enemy2, 50, 30);
var template = new Equipments.SubMunitionMissile();
var equipment = template.generateFixed(0);
equipment.distance = 5;
equipment.blast = 1.5;
(<DamageEffect>equipment.target_effects[0]).value = 20;
var equipment = template.generate(1);
let action = <FireWeaponAction>equipment.action;
action.range = 5;
action.blast = 1.5;
(<DamageEffect>action.effects[0]).value = 20;
var checkHP = (h1: number, s1: number, h2: number, s2: number, h3: number, s3: number): void => {
expect(ship.values.hull.get()).toBe(h1);

View file

@ -1,15 +1,14 @@
/// <reference path="AbstractWeapon.ts"/>
/// <reference path="../LootTemplate.ts"/>
module TS.SpaceTac.Equipments {
export class SubMunitionMissile extends AbstractWeapon {
export class SubMunitionMissile extends LootTemplate {
constructor() {
super("SubMunition Missile", 30, 50);
super(SlotType.Weapon, "SubMunition Missile");
this.setRange(500, 700, true);
this.setBlast(150, 200);
this.ap_usage = new IntegerRange(4, 5);
this.min_level = new IntegerRange(1, 3);
this.setSkillsRequirements({ "skill_material": 1 });
this.addFireAction(irepeat(4), istep(500, irepeat(20)), istep(150, irepeat(5)), [
new EffectTemplate(new DamageEffect(), { "value": istep(30, irepeat(2)) })
]);
}
}
}

View file

@ -19,16 +19,16 @@ module TS.SpaceTac.UI.Specs {
expect(bar.action_icons[0].action.code).toEqual("endturn");
// Add an engine, with move action
ship.addSlot(SlotType.Engine).attach((new Equipments.ConventionalEngine()).generate());
TestTools.addEngine(ship, 50);
bar.setShip(ship);
expect(bar.action_icons.length).toBe(2);
expect(bar.action_icons[0].action.code).toEqual("move");
// Add a weapon, with fire action
ship.addSlot(SlotType.Weapon).attach((new Equipments.GatlingGun()).generate());
TestTools.addWeapon(ship, 10, 1, 100);
bar.setShip(ship);
expect(bar.action_icons.length).toBe(3);
expect(bar.action_icons[1].action.code).toEqual("fire-gatlinggun");
expect(bar.action_icons[1].action.code).toEqual("fire-equipment");
});
it("mark actions that would become unavailable after use", function () {
@ -37,16 +37,9 @@ module TS.SpaceTac.UI.Specs {
var ship = new Ship();
ship.arena_x = 1;
ship.arena_y = 8;
var engine = (new Equipments.ConventionalEngine()).generate();
engine.ap_usage = 8;
engine.distance = 4;
ship.addSlot(SlotType.Engine).attach(engine);
var weapon1 = (new Equipments.GatlingGun()).generate();
weapon1.ap_usage = 3;
ship.addSlot(SlotType.Weapon).attach(weapon1);
var weapon2 = (new Equipments.GatlingGun()).generate();
weapon2.ap_usage = 5;
ship.addSlot(SlotType.Weapon).attach(weapon2);
var engine = TestTools.addEngine(ship, 0.5);
var weapon1 = TestTools.addWeapon(ship, 100, 3);
var weapon2 = TestTools.addWeapon(ship, 100, 5);
battleview.battle.playing_ship = ship;
battleview.player = ship.getPlayer();

View file

@ -52,11 +52,12 @@ module TS.SpaceTac.UI {
this.position.set(action.x, action.y + action.height + 44);
this.main_title.setText(action.action.equipment ? action.action.equipment.name : action.action.name);
this.sub_title.setText(action.action.equipment ? action.action.name : "");
let cost = action.action.equipment ? `Cost: ${action.action.equipment.ap_usage} power` : "";
if (action.action instanceof MoveAction) {
cost += ` per ${action.action.equipment.distance}km`;
this.cost.setText(`Cost: 1 power per ${action.action.distance_per_power}km`);
} else {
let cost = action.action.getActionPointsUsage(action.ship, null);
this.cost.setText(cost == 0 ? "" : `Cost: ${cost} power`);
}
this.cost.setText(cost);
this.description.setText(action.action.equipment ? action.action.equipment.getActionDescription() : "");
let position = this.bar.action_icons.indexOf(action);

View file

@ -127,13 +127,15 @@ module TS.SpaceTac.UI {
missile.rotation = this.source.getAngleTo(this.destination);
this.layer.addChild(missile);
let blast_radius = this.weapon.action.getBlastRadius(this.source.ship || new Ship());
let tween = this.ui.tweens.create(missile);
tween.to({ x: this.destination.x, y: this.destination.y }, 1000);
tween.onComplete.addOnce(() => {
missile.destroy();
if (this.weapon.blast) {
if (blast_radius > 0) {
let blast = new Phaser.Image(this.ui, this.destination.x, this.destination.y, "battle-weapon-blast");
let scaling = this.weapon.blast * 2 / (blast.width * 0.9);
let scaling = blast_radius * 2 / (blast.width * 0.9);
blast.anchor.set(0.5, 0.5);
blast.scale.set(0.001, 0.001);
let tween1 = this.ui.tweens.create(blast.scale).to({ x: scaling, y: scaling }, 1500, Phaser.Easing.Quintic.Out);
@ -146,7 +148,7 @@ module TS.SpaceTac.UI {
});
tween.start();
return 1000 + (this.weapon.blast ? 1500 : 0);
return 1000 + (blast_radius ? 1500 : 0);
}
/**

View file

@ -50,8 +50,8 @@ module TS.SpaceTac.UI {
return 0;
}
addEquipment(equipment: CharacterEquipment, source: CharacterEquipmentContainer | null, test: boolean): boolean {
if (this.ship != this.sheet.ship && equipment.item.slot !== null) {
let slot = this.ship.getFreeSlot(equipment.item.slot);
if (this.ship != this.sheet.ship && equipment.item.slot_type !== null) {
let slot = this.ship.getFreeSlot(equipment.item.slot_type);
if (slot) {
if (test) {
return true;

View file

@ -36,7 +36,7 @@ module TS.SpaceTac.UI {
return 66;
}
addEquipment(equipment: CharacterEquipment, source: CharacterEquipmentContainer | null, test: boolean): boolean {
if (equipment.item.slot !== null && this.sheet.ship.getFreeSlot(equipment.item.slot)) {
if (equipment.item.slot_type !== null && this.sheet.ship.getFreeSlot(equipment.item.slot_type)) {
if (test) {
return true;
} else {
@ -47,7 +47,7 @@ module TS.SpaceTac.UI {
}
}
removeEquipment(equipment: CharacterEquipment, destination: CharacterEquipmentContainer | null, test: boolean): boolean {
if (contains(this.sheet.ship.listEquipment(equipment.item.slot), equipment.item)) {
if (contains(this.sheet.ship.listEquipment(equipment.item.slot_type), equipment.item)) {
if (test) {
return true;
} else {