character sheet: Fixed a possible loss of equipment
This commit is contained in:
parent
2fb696544f
commit
eb28591abd
|
@ -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
4
TODO
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -13,6 +13,10 @@ module TS.SpaceTac.UI {
|
|||
this.sheet = sheet;
|
||||
}
|
||||
|
||||
jasmineToString() {
|
||||
return "CharacterCargo";
|
||||
}
|
||||
|
||||
/**
|
||||
* CharacterEquipmentContainer interface
|
||||
*/
|
||||
|
|
119
src/ui/character/CharacterEquipment.spec.ts
Normal file
119
src/ui/character/CharacterEquipment.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
38
src/ui/character/CharacterSlot.spec.ts
Normal file
38
src/ui/character/CharacterSlot.spec.ts
Normal 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([]);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue