1
0
Fork 0

character sheet: Fixed a possible loss of equipment

This commit is contained in:
Michaël Lemaire 2017-04-25 20:24:43 +02:00
parent 2fb696544f
commit eb28591abd
9 changed files with 248 additions and 30 deletions

View file

@ -24,6 +24,7 @@ After making changes to sources, you need to recompile:
## Credits
* **[Michaël Lemaire](https://thunderk.net/)** - Code and graphics
* **[Matthieu Desprez](https://github.com/edistra)** - Beta testing and ideas
* **Nicolas Forgo** - Ship models
* **[Phaser](http://phaser.io)** - Game engine
* **[Kevin MacLeod](http://www.incompetech.com/)** - Musics

4
TODO
View file

@ -3,7 +3,7 @@
* Character sheet: disable interaction during battle (except for loot screen)
* Character sheet: paginate loot and shop items
* Character sheet: improve eye-catching for shop and loot section
* Character sheet: equipping an item without the requirements make it disappear
* Character sheet: highlight allowed destinations during drag-and-drop
* 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
@ -36,6 +36,7 @@
* AI: apply safety distances to move actions
* AI: use support equipments (repair drones...)
* AI: fix not being able to apply simulated maneuver
* AI: do not always move first, they are defenders
* TacticalAI: allow to play several moves in the same turn
* TacticalAI: add pauses to not play too quickly
* TacticalAI: replace BullyAI
@ -55,5 +56,6 @@
Later, if possible:
* Replays
* Multiplayer
* Formation or deployment phase
* Saving to external file
* Saving to cloud

View file

@ -414,6 +414,30 @@ module TS.SpaceTac.Specs {
expect(ship.cargo).toEqual([equipment]);
});
it("checks equipment requirements", function () {
let ship = new Ship();
let equipment = new Equipment(SlotType.Hull);
expect(ship.canEquip(equipment)).toBe(null);
ship.addSlot(SlotType.Engine);
expect(ship.canEquip(equipment)).toBe(null);
let slot = ship.addSlot(SlotType.Hull);
expect(ship.canEquip(equipment)).toBe(slot);
equipment.requirements["skill_energy"] = 2;
expect(ship.canEquip(equipment)).toBe(null);
ship.upgradeSkill("skill_energy");
expect(ship.canEquip(equipment)).toBe(null);
ship.upgradeSkill("skill_energy");
expect(ship.canEquip(equipment)).toBe(slot);
slot.attach(new Equipment(SlotType.Hull));
expect(ship.canEquip(equipment)).toBe(null);
});
it("allow skills upgrading from current level", function () {
let ship = new Ship();
expect(ship.level.get()).toBe(1);

View file

@ -511,19 +511,37 @@ 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_type && !slot.attached);
let free_slot = this.canEquip(item);
if (free_slot && (!from_cargo || remove(this.cargo, item))) {
free_slot.attach(item);
this.updateAttributes();
return true;
if (item.attached_to == free_slot && free_slot.attached == item) {
this.updateAttributes();
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* Check if a ship is able to equip en item, and return the slot it may fit in, or null
*/
canEquip(item: Equipment): Slot | null {
let free_slot = first(this.slots, slot => slot.type == item.slot_type && !slot.attached);
if (free_slot) {
if (item.canBeEquipped(this)) {
return free_slot;
} else {
return null;
}
} else {
return null;
}
}
/**
* Remove an equipped item, returning it to cargo
*

View file

@ -13,6 +13,10 @@ module TS.SpaceTac.UI {
this.sheet = sheet;
}
jasmineToString() {
return "CharacterCargo";
}
/**
* CharacterEquipmentContainer interface
*/

View file

@ -0,0 +1,119 @@
module TS.SpaceTac.UI.Specs {
describe("CharacterEquipment", function () {
let testgame = setupEmptyView();
class FakeContainer implements CharacterEquipmentContainer {
name: string;
x: number;
inside: CharacterEquipment | null;
constructor(name: string, x: number) {
this.name = name;
this.x = x;
this.inside = null;
}
jasmineToString() {
return this.name;
}
isInside(x: number, y: number): boolean {
return x == this.x;
}
getEquipmentAnchor(): { x: number, y: number, scale: number } {
return {
x: this.x,
y: 0,
scale: 0.5
}
}
getPriceOffset(): number {
return 12;
}
addEquipment(equipment: CharacterEquipment, source: CharacterEquipmentContainer | null, test: boolean): boolean {
if (this.x < 150) {
if (!test) {
this.inside = equipment;
}
return true;
} else {
return false;
}
}
removeEquipment(equipment: CharacterEquipment, destination: CharacterEquipmentContainer | null, test: boolean): boolean {
if (this.x < 150 && this.inside == equipment) {
if (!test) {
this.inside = null;
}
return true;
} else {
return false;
}
}
}
it("handles drag-and-drop to move equipment", function () {
let view = testgame.baseview;
let sheet = new CharacterSheet(view);
sheet.show(new Ship());
let refresh = spyOn(sheet, "refresh").and.stub();
let container1 = new FakeContainer("container1", 0);
let container2 = new FakeContainer("container2", 100);
let container3 = new FakeContainer("container3", 200);
let equipment = new CharacterEquipment(sheet, new Equipment(), container1);
container1.inside = equipment;
spyOn(sheet, "iEquipmentContainers").and.returnValue(iarray([container1, container2, container3]));
expect(equipment.container).toBe(container1);
expect(equipment.x).toBe(0);
expect(equipment.scale.x).toBe(0.25);
// drop on nothing
equipment.events.onDragStart.dispatch();
equipment.x = 812;
equipment.events.onDragStop.dispatch();
expect(equipment.container).toBe(container1);
expect(equipment.x).toBe(0);
expect(refresh).toHaveBeenCalledTimes(0);
// drop on accepting destination
equipment.events.onDragStart.dispatch();
equipment.x = 100;
equipment.events.onDragStop.dispatch();
expect(equipment.container).toBe(container2);
expect(equipment.x).toBe(100);
expect(container1.inside).toBe(null);
expect(container2.inside).toBe(equipment);
expect(refresh).toHaveBeenCalledTimes(1);
// drop on refusing destination
equipment.events.onDragStart.dispatch();
equipment.x = 200;
equipment.events.onDragStop.dispatch();
expect(equipment.container).toBe(container2);
expect(equipment.x).toBe(100);
expect(container2.inside).toBe(equipment);
expect(container3.inside).toBe(null);
expect(refresh).toHaveBeenCalledTimes(1);
// broken destination, should return to source
let log = spyOn(console, "error").and.stub();
spyOn(container3, "addEquipment").and.returnValues(true, false, true, false);
equipment.events.onDragStart.dispatch();
equipment.x = 200;
equipment.events.onDragStop.dispatch();
expect(equipment.container).toBe(container2);
expect(equipment.x).toBe(100);
expect(refresh).toHaveBeenCalledTimes(1);
expect(log).toHaveBeenCalledWith('Destination container refused to accept equipment', equipment, container2, container3);
// broken destination and source, item is lost !
spyOn(container2, "addEquipment").and.returnValue(false);
equipment.events.onDragStart.dispatch();
equipment.x = 200;
equipment.events.onDragStop.dispatch();
expect(equipment.container).toBe(container3);
expect(equipment.x).toBe(200);
expect(refresh).toHaveBeenCalledTimes(2);
expect(log).toHaveBeenCalledWith('Equipment lost in bad exchange !', equipment, container2, container3);
});
});
}

View file

@ -43,8 +43,6 @@ module TS.SpaceTac.UI {
this.container = container;
this.price = 0;
this.container.addEquipment(this, null, false);
this.anchor.set(0.5, 0.5);
this.setupDragDrop(sheet);
@ -53,6 +51,10 @@ module TS.SpaceTac.UI {
sheet.view.tooltip.bind(this, container => this.fillTooltip(container));
}
jasmineToString() {
return this.item.jasmineToString();
}
/**
* Find the container under a specific screen location
*/
@ -103,17 +105,16 @@ module TS.SpaceTac.UI {
this.scale.set(0.5, 0.5);
this.alpha = 0.8;
});
this.events.onDragUpdate.add(() => {
let destination = this.findContainerAt(this.x, this.y);
if (destination) {
this.applyDragDrop(this.container, destination, true);
}
});
this.events.onDragStop.add(() => {
let destination = this.findContainerAt(this.x, this.y);
if (destination) {
this.applyDragDrop(this.container, destination, false);
sheet.refresh();
if (destination && destination != this.container) {
if (this.applyDragDrop(this.container, destination, false)) {
this.container = destination;
this.snapToContainer();
sheet.refresh(); // TODO Only if required (destination is "virtual")
} else {
this.snapToContainer();
}
} else {
this.snapToContainer();
}
@ -122,22 +123,33 @@ module TS.SpaceTac.UI {
/**
* Apply drag and drop between two containers
*
* Return true if something changed (or would change, if test=true).
*/
applyDragDrop(source: CharacterEquipmentContainer, destination: CharacterEquipmentContainer, hold: boolean) {
if (source.removeEquipment(this, destination, true) && destination.addEquipment(this, source, true)) {
if (!hold) {
if (source.removeEquipment(this, destination, false)) {
if (!destination.addEquipment(this, source, false)) {
console.error("Destination container refused to accept equipment", this, source, destination);
// Go back to source
if (!source.addEquipment(this, null, true)) {
console.error("Equipment lost in bad exchange !", this, source, destination);
}
}
applyDragDrop(source: CharacterEquipmentContainer, destination: CharacterEquipmentContainer, test: boolean): boolean {
let possible = source.removeEquipment(this, destination, true) && destination.addEquipment(this, source, true);
if (test) {
return possible;
} else if (possible) {
if (source.removeEquipment(this, destination, false)) {
if (destination.addEquipment(this, source, false)) {
return true;
} else {
console.error("Source container refused to give away equipment", this, source, destination);
console.error("Destination container refused to accept equipment", this, source, destination);
// Go back to source
if (source.addEquipment(this, null, false)) {
return false;
} else {
console.error("Equipment lost in bad exchange !", this, source, destination);
return true;
}
}
} else {
console.error("Source container refused to give away equipment", this, source, destination);
return false;
}
} else {
return false;
}
}

View file

@ -0,0 +1,38 @@
module TS.SpaceTac.UI.Specs {
describe("CharacterSlot", function () {
let testgame = setupEmptyView();
it("allows dragging equipment", function () {
let view = testgame.baseview;
let ship = new Ship();
ship.addSlot(SlotType.Hull);
let sheet = new CharacterSheet(view);
sheet.show(ship);
let source = new CharacterLootSlot(sheet, 0, 0);
sheet.addChild(source);
let equipment = new CharacterEquipment(sheet, new Equipment(SlotType.Engine), source);
let slot = new CharacterSlot(sheet, 0, 0, SlotType.Engine);
expect(slot.addEquipment(equipment, source, true)).toBe(false);
expect(slot.removeEquipment(equipment, source, true)).toBe(false);
ship.addSlot(SlotType.Engine);
expect(slot.addEquipment(equipment, source, true)).toBe(true);
equipment.item.requirements["skill_time"] = 1;
expect(slot.addEquipment(equipment, source, true)).toBe(false);
ship.upgradeSkill("skill_time");
expect(slot.addEquipment(equipment, source, true)).toBe(true);
expect(ship.listEquipment(SlotType.Engine)).toEqual([]);
let result = slot.addEquipment(equipment, source, false);
expect(result).toBe(true);
expect(ship.listEquipment(SlotType.Engine)).toEqual([equipment.item]);
result = slot.removeEquipment(equipment, source, false);
expect(result).toBe(true);
expect(ship.listEquipment(SlotType.Engine)).toEqual([]);
});
});
}

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_type !== null && this.sheet.ship.getFreeSlot(equipment.item.slot_type)) {
if (this.sheet.ship.canEquip(equipment.item)) {
if (test) {
return true;
} else {