Skip to content

Commit

Permalink
fix: add consumables
Browse files Browse the repository at this point in the history
add consumables and  various  ways of handling them.

Fix xdy#212
  • Loading branch information
jonepatr committed Jan 27, 2021
1 parent 4c21144 commit 36b9e78
Show file tree
Hide file tree
Showing 29 changed files with 709 additions and 104 deletions.
5 changes: 5 additions & 0 deletions src/module/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ const ROLLTYPES = Object.freeze({
Disadvantage: {key: 'Disadvantage', formula: "3d6kl2"}
});

const CONSUMABLES = Object.freeze([
"air", "drugs", "food", "fuel", "magazine", "power_cell", "other"
]);

const DIFFICULTIES = Object.freeze({
CE: {
Simple: {mod: 6, target: 2},
Expand All @@ -114,6 +118,7 @@ const DIFFICULTIES = Object.freeze({

export const TWODSIX = {
CHARACTERISTICS: CHARACTERISTICS,
CONSUMABLES: CONSUMABLES,
VARIANTS: VARIANTS,
ROLLTYPES: ROLLTYPES,
DIFFICULTIES: DIFFICULTIES,
Expand Down
79 changes: 74 additions & 5 deletions src/module/entities/TwodsixItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,38 @@ export default class TwodsixItem extends Item {
if (this.getFlag("twodsix", "untrainedSkill")) {
this.data.name = game.i18n.localize("TWODSIX.Actor.Skills.Untrained");
}
if (this.data.data.consumables) {
this.data.data.consumableData = this.data.data.consumables.map((consumableId:string) => {
// this is a bit hacky.. seems like the actor has not been initialized fully at this point.
return this.actor.data["items"].filter((item:TwodsixItem) => item._id === consumableId)[0];
});
this.data.data.consumableData.sort((a:TwodsixItem, b:TwodsixItem) => {
return ((a.name > b.name) ? -1 : ((a.name > b.name) ? 1 : 0));
});
}
}

public async addConsumable(consumableId:string):Promise<void> {
if (this.data.data.consumables.includes(consumableId)) {
console.error(`Twodsix | Consumable already exists for item ${this._id}`);
return;
}
await this.update({"data.consumables": this.data.data.consumables.concat(consumableId)}, {});
}

public async removeConsumable(consumableId: string):Promise<void> {
const updatedConsumables = this.data.data.consumables.filter((cId:string) => {
return cId !== consumableId;
});
const updateData = {"data.consumables": updatedConsumables};
if (this.data.data.useConsumableForAttack === consumableId) {
updateData["data.useConsumableForAttack"] = "";
}
await this.update(updateData, {});
}

//////// WEAPON ////////

public async performAttack(attackType:string, showThrowDialog:boolean, rateOfFireCE:number = null, showInChat = true):Promise<void> {
if (this.type !== "weapon") {
return;
Expand All @@ -34,26 +64,31 @@ export default class TwodsixItem extends Item {

let numberOfAttacks = 1;
let bonusDamage = "0";
const rateOfFire = this.data.data.rateOfFire;

const rof = parseInt(this.data.data.rateOfFire, 10);
const rateOfFire:number = rateOfFireCE ?? (!isNaN(rof) ? rof : 0);
if (attackType && !rateOfFire) {
ui.notifications.error(game.i18n.localize("TWODSIX.Errors.NoROFForAttack"));
}
const skill:TwodsixItem = this.actor.getOwnedItem(this.data.data.skill) as TwodsixItem;
const tmpSettings = {"characteristic": skill?.data.data.characteristic || 'NONE'};

let usedAmmo = 1;
switch (attackType) {
case "auto-full":
numberOfAttacks = parseInt(rateOfFire, 10);
numberOfAttacks = rateOfFire;
usedAmmo = 3 * rateOfFire;
break;
case "auto-burst":
bonusDamage = rateOfFire;
bonusDamage = rateOfFire.toString();
usedAmmo = parseInt(this.data.data.rateOfFire, 10);
break;
case "burst-attack-dm":
tmpSettings["diceModifier"] = TwodsixItem.burstAttackDM(rateOfFireCE);
usedAmmo = rateOfFireCE;
break;
case "burst-bonus-damage":
bonusDamage = TwodsixItem.burstBonusDamage(rateOfFireCE);
usedAmmo = rateOfFireCE;
break;
}

Expand All @@ -63,6 +98,20 @@ export default class TwodsixItem extends Item {
return;
}

if (this.data.data.useConsumableForAttack) {
const magazine = this.actor.getOwnedItem(this.data.data.useConsumableForAttack) as TwodsixItem;
try {
await magazine.consume(usedAmmo);
} catch(err) {
if (err.name == "NoAmmoError") {
ui.notifications.error(game.i18n.localize("TWODSIX.Errors.NoAmmo"));
return;
} else {
throw err;
}
}
}

for (let i = 0; i < numberOfAttacks; i++) {
const roll = await this.skillRoll(false, settings, showInChat);
if (game.settings.get("twodsix", "automateDamageRollOnHit") && roll.isSuccess()) {
Expand All @@ -71,7 +120,6 @@ export default class TwodsixItem extends Item {
}
}


public async skillRoll(showThrowDialog:boolean, tmpSettings:TwodsixRollSettings = null, showInChat = true):Promise<TwodsixDiceRoll> {
let skill:TwodsixItem;
let item:TwodsixItem;
Expand Down Expand Up @@ -179,4 +227,25 @@ export default class TwodsixItem extends Item {
return '0';
}
}

//////// CONSUMABLE ////////
public async consume(quantity:number):Promise<void> {
const consumableLeft = this.data.data.currentCount - quantity;
if (consumableLeft >= 0) {
await this.update({"data.currentCount": consumableLeft}, {});
} else {
throw {name: 'NoAmmoError'};
}
}

public async refill():Promise<void> {
if (this.data.data.quantity > 1) {
await this.update({
"data.quantity": this.data.data.quantity - 1,
"data.currentCount": this.data.data.max
}, {});
} else {
throw {name: 'TooLowQuantityError'};
}
}
}
62 changes: 35 additions & 27 deletions src/module/handlebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ export default function registerHandlebarsHelpers():void {
}
});

Handlebars.registerHelper('twodsix_localizeConsumable', (type) => {
return game.i18n.localize(`TWODSIX.Items.Consumable.Types.${type}`);
});

Handlebars.registerHelper('twodsix_refillText', (subtype, quantity) => {
const refillWord = ["magazine", "power_cell"].includes(subtype) ? "Reload" : "Refill";
return `${game.i18n.localize(`TWODSIX.Actor.Items.${refillWord}`)} (${quantity - 1})`;
});

Handlebars.registerHelper('twodsix_skillTotal', (actor, characteristic, value) => {
const actorData = actor.data;
const characteristicElement = actorData.characteristics[getKeyByValue(TWODSIX.CHARACTERISTICS, characteristic)];
Expand Down Expand Up @@ -99,32 +108,31 @@ export default function registerHandlebarsHelpers():void {
});

//From https://discord.com/channels/732325252788387980/732328233630171188/790507540818690068
//Not used yet
// Handlebars.registerHelper("iff", function (v1, operator, v2, options) {
// switch (operator) {
// case '==':
// return (v1 == v2) ? options.fn(this) : options.inverse(this);
// case '===':
// return (v1 === v2) ? options.fn(this) : options.inverse(this);
// case '!=':
// return (v1 != v2) ? options.fn(this) : options.inverse(this);
// case '!==':
// return (v1 !== v2) ? options.fn(this) : options.inverse(this);
// case '<':
// return (v1 < v2) ? options.fn(this) : options.inverse(this);
// case '<=':
// return (v1 <= v2) ? options.fn(this) : options.inverse(this);
// case '>':
// return (v1 > v2) ? options.fn(this) : options.inverse(this);
// case '>=':
// return (v1 >= v2) ? options.fn(this) : options.inverse(this);
// case '&&':
// return (v1 && v2) ? options.fn(this) : options.inverse(this);
// case '||':
// return (v1 || v2) ? options.fn(this) : options.inverse(this);
// default:
// return options.inverse(this);
// }
// });
Handlebars.registerHelper("iff", function (v1, operator, v2, options) {
switch (operator) {
case '==':
return (v1 == v2) ? options.fn(this) : options.inverse(this);
case '===':
return (v1 === v2) ? options.fn(this) : options.inverse(this);
case '!=':
return (v1 != v2) ? options.fn(this) : options.inverse(this);
case '!==':
return (v1 !== v2) ? options.fn(this) : options.inverse(this);
case '<':
return (v1 < v2) ? options.fn(this) : options.inverse(this);
case '<=':
return (v1 <= v2) ? options.fn(this) : options.inverse(this);
case '>':
return (v1 > v2) ? options.fn(this) : options.inverse(this);
case '>=':
return (v1 >= v2) ? options.fn(this) : options.inverse(this);
case '&&':
return (v1 && v2) ? options.fn(this) : options.inverse(this);
case '||':
return (v1 || v2) ? options.fn(this) : options.inverse(this);
default:
return options.inverse(this);
}
});

}
12 changes: 12 additions & 0 deletions src/module/hooks/deleteItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import TwodsixActor from "../entities/TwodsixActor";
import TwodsixItem from "../entities/TwodsixItem";

Hooks.on('deleteOwnedItem', async (actor:TwodsixActor, itemData) => {
if (itemData.type === "consumable") {
actor.items.filter((item:TwodsixItem) => item.type !== "skills").forEach(async (item:TwodsixItem) => {
if (item.data.data.consumables.includes(itemData._id) || item.data.data.useConsumableForAttack === itemData._id) {
await item.removeConsumable(itemData._id);
}
});
}
});
7 changes: 7 additions & 0 deletions src/module/hooks/updateItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import TwodsixActor from "../entities/TwodsixActor";

Hooks.on('preUpdateOwnedItem', async (actor:TwodsixActor, oldItem: Record<string,any>, updateData: Record<string,any>) => {
if (updateData.type && oldItem.type === "weapon" && updateData.type !== "weapon" && oldItem.data.useConsumableForAttack) {
updateData["data.useConsumableForAttack"] = "";
}
});
66 changes: 27 additions & 39 deletions src/module/sheets/AbstractTwodsixActorSheet.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { getDataFromDropEvent, getItemDataFromDropData } from "../utils/sheetUtils";

export abstract class AbstractTwodsixActorSheet extends ActorSheet {

/** @override */
Expand Down Expand Up @@ -77,6 +79,23 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet {
}


private updateWithItemSpecificValues(itemData:Record<string, any>, type:string): void {
switch (type) {
case "skills":
if (!game.settings.get('twodsix', 'hideUntrainedSkills')) {
itemData.data.value = game.system.template.Item.skills.value;
} else {
itemData.data.value = 0;
}
break;
case "weapon":
if (game.settings.get('twodsix', 'hideUntrainedSkills')) {
itemData.data.skill = this.actor.getUntrainedSkill().id;
}
break;
}
}

/**
* Handle creating a new Owned Item for the actor using initial data defined in the HTML dataset
* @param {Event} event The originating click event
Expand All @@ -101,18 +120,7 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet {

// Remove the type from the dataset since it's in the itemData.type prop.
delete itemData.data.type;

if (itemData.type === 'skills') {
if (!game.settings.get('twodsix', 'hideUntrainedSkills')) {
itemData.data.value = game.system.template.Item.skills.value;
} else {
itemData.data.value = 0;
}
}

if (game.settings.get('twodsix', 'hideUntrainedSkills') && type === "weapon") {
itemData.data.skill = this.actor.getUntrainedSkill().id;
}
this.updateWithItemSpecificValues(itemData, type);

// Finally, create the item!
return this.actor.createOwnedItem(itemData);
Expand All @@ -125,16 +133,7 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet {
protected async _onDrop(event:DragEvent):Promise<boolean | any> {
event.preventDefault();

let data:any;
try {
if (!event.dataTransfer) {
return false;
}
data = JSON.parse(event.dataTransfer.getData('text/plain'));
} catch (err) {
console.log(`Twodsix | Drop failed with {err}`);
return false;
}
const data = getDataFromDropEvent(event);

if (!data) {
console.log(`Twodsix | Dragging something that can't be dragged`);
Expand All @@ -147,23 +146,7 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet {
}

const actor = this.actor;
let itemData;

if (data.pack) {
// compendium
const pack = game.packs.find((p) => p.collection === data.pack);
if (pack.metadata.entity !== 'Item') {
return;
}
const item = await pack.getEntity(data.id);
itemData = duplicate(item.data);
} else if (data.data) {
// other actor
itemData = duplicate(data.data);
} else {
// items directory
itemData = duplicate(game.items.get(data.id).data);
}
const itemData = await getItemDataFromDropData(data);


//If we get here, we're sorting things.
Expand Down Expand Up @@ -221,6 +204,7 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet {
const tool:Item[] = [];
const junk:Item[] = [];
const skills:Item[] = [];
const consumable:Item[] = [];

// Iterate through items, allocating to containers
for (const i of sheetData.items) {
Expand All @@ -246,6 +230,9 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet {
case 'skills':
skills.push(i);
break;
case 'consumable':
consumable.push(i);
break;
default:
break;
}
Expand All @@ -258,6 +245,7 @@ export abstract class AbstractTwodsixActorSheet extends ActorSheet {
actorData.augment = augment;
actorData.tool = tool;
actorData.junk = junk;
actorData.consumable = consumable;
actorData.skills = skills;

}
Expand Down
Loading

0 comments on commit 36b9e78

Please sign in to comment.