Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Xp spending #1398

Merged
merged 5 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
"SWFFG.TabBiography": "Biography",
"SWFFG.TabDescription": "Description",
"SWFFG.TabModifiers": "Modifiers",
"SWFFG.TabSpecializations": "Specializations",
"SWFFG.TabCareer": "Career Data",
"SWFFG.CharacteristicBrawn": "Brawn",
"SWFFG.CharacteristicBrawnAbbreviation": "Br",
"SWFFG.CharacteristicAgility": "Agility",
Expand Down Expand Up @@ -643,5 +645,35 @@
"SWFFG.Settings.showAdversaryCount.Name": "Show Adversary Count",
"SWFFG.Settings.showAdversaryCount.Hint": "Shows the ranks of the Adversary talent on tokens",
"SWFFG.Settings.AdversaryItemName.Name": "Adversary Item Name",
"SWFFG.Settings.AdversaryItemName.Hint": "The name of the talent to consider 'adversary' for the purposes of showing ranks on tokens (for translations)"
"SWFFG.Settings.AdversaryItemName.Hint": "The name of the talent to consider 'adversary' for the purposes of showing ranks on tokens (for translations)",
"SWFFG.Actors.Sheets.Purchase.LogTitle": "XP Spend Log",
"SWFFG.Actors.Sheets.Purchase.DialogTitle": "Purchase {itemType}",
"SWFFG.Actors.Sheets.Purchase.ConfirmPurchase": "Purchase",
"SWFFG.Actors.Sheets.Purchase.CancelPurchase": "Cancel",
"SWFFG.Actors.Sheets.Purchase.NotEnoughXP": "You do not have enough XP to buy a rank in this skill",
"SWFFG.Actors.Sheets.Purchase.SkillRank.ContextMenuText": "Purchase Skill Rank",
"SWFFG.Actors.Sheets.Purchase.SkillRank.ConfirmTitle": "Purchase Skill Rank",
"SWFFG.Actors.Sheets.Purchase.SkillRank.Text": "Spend {cost} XP to purchase a rank in {skill}?",
"SWFFG.Actors.Sheets.Purchase.Talent.ContextMenuText": "Purchase Talent",
"SWFFG.Actors.Sheets.Purchase.Talent.ConfirmTitle": "Purchase Talent",
"SWFFG.Actors.Sheets.Purchase.Talent.ConfirmText": "Spend {cost} XP to purchase {talent}?",
"SWFFG.Actors.Sheets.Purchase.FP.ContextMenuText": "Purchase Upgrade",
"SWFFG.Actors.Sheets.Purchase.FP.ConfirmTitle": "Purchase Upgrade",
"SWFFG.Actors.Sheets.Purchase.FP.ConfirmText": "Spend {cost} XP to purchase {upgrade}?",
"SWFFG.Actors.Sheets.Purchase.SA.ContextMenuText": "Purchase Upgrade",
"SWFFG.Actors.Sheets.Purchase.SA.ConfirmTitle": "Purchase Upgrade",
"SWFFG.Actors.Sheets.Purchase.SA.ConfirmText": "Spend {cost} XP to purchase {upgrade}?",
"SWFFG.Actors.Sheets.Purchase.SA.NotSet": "Please set career signature abilities in the career data tab on your career",
"SWFFG.Actors.Sheets.Purchase.Talent.New.ContextMenuText": "Purchase Specialization",
"SWFFG.Actors.Sheets.Purchase.Career.Specialization.DropHint": "(drop specializations here)",
"SWFFG.Actors.Sheets.Purchase.Career.Specializations.Header": "Specializations in this Career",
"SWFFG.Actors.Sheets.Purchase.Career.SignatureAbility.DropHint": "(drop signature abilities here)",
"SWFFG.Actors.Sheets.Purchase.Career.SignatureAbility.Header": "Signature Abilities in this Career",
"SWFFG.Actors.Sheets.Purchase.Dropdown": "Select a {itemType}...",
"SWFFG.Settings.Purchase.Specialization.Name": "Specialization Compendiums",
"SWFFG.Settings.Purchase.Specialization.Hint": "Comma seperated compendiums to search for buying Specializations (no spaces)",
"SWFFG.Settings.Purchase.ForcePower.Name": "Force Power Compendiums",
"SWFFG.Settings.Purchase.ForcePower.Hint": "Comma seperated compendiums to search for buying Force Powers (no spaces)",
"SWFFG.Settings.Purchase.SignatureAbility.Name": "Signature Abilities Compendiums",
"SWFFG.Settings.Purchase.SignatureAbility.Hint": "Comma seperated compendiums to search for buying Signature Abilities (no spaces)"
}
238 changes: 236 additions & 2 deletions modules/actors/actor-sheet-ffg.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import DiceHelpers from "../helpers/dice-helpers.js";
import ActorOptions from "./actor-ffg-options.js";
import ImportHelpers from "../importer/import-helpers.js";
import ModifierHelpers from "../helpers/modifiers.js";
import ActorHelpers from "../helpers/actor-helpers.js";
import ActorHelpers, {xpLogSpend} from "../helpers/actor-helpers.js";
import ItemHelpers from "../helpers/item-helpers.js";
import EmbeddedItemHelpers from "../helpers/embeddeditem-helpers.js";
import {change_role, deregister_crew, build_crew_roll} from "../helpers/crew.js";
Expand Down Expand Up @@ -147,6 +147,9 @@ export class ActorSheetFFG extends ActorSheet {
if (this.actor.flags?.config?.enableObligation === false && this.actor.flags?.config?.enableDuty === false && this.actor.flags?.config?.enableMorality === false && this.actor.flags?.config?.enableConflict === false) {
data.hideObligationDutyMoralityConflictTab = true;
}
if (this.actor.flags?.starwarsffg?.xpLog) {
data.xpLog = this.actor.flags.starwarsffg.xpLog.join("<br>");
}

return data;
}
Expand Down Expand Up @@ -264,6 +267,13 @@ export class ActorSheetFFG extends ActorSheet {
this._onRemoveSkill(li);
},
},
{
name: game.i18n.localize("SWFFG.Actors.Sheets.Purchase.SkillRank.ContextMenuText"),
icon: '<i class="fas fa-dollar"></i>',
callback: (li) => {
this._buySkillRank(li);
},
},
]);

new ContextMenu(html, "div.skillsHeader", [
Expand All @@ -276,6 +286,10 @@ export class ActorSheetFFG extends ActorSheet {
},
]);

html.find("td.ffg-purchase").click(async (ev) => {
await this._buyCore(ev)
});

// Send Item Details to chat.

const sendToChatContextItem = {
Expand Down Expand Up @@ -959,7 +973,6 @@ export class ActorSheetFFG extends ActorSheet {
html.find(".force-conflict .enable-dice-pool").on("click", async (event) => {
event.preventDefault();
await this.actor.setFlag('starwarsffg', 'config', {enableForcePool: true});
console.log({this: this, event: event})
});

html.find(".force-conflict .remove-force-powers").on("click", async (event) => {
Expand Down Expand Up @@ -1194,6 +1207,57 @@ export class ActorSheetFFG extends ActorSheet {
).render(true);
}

/**
* Handle the right click -> buy skill rank event
* @param a - Event object
* @returns {Promise<void>}
* @private
*/
async _buySkillRank(a) {
const skill = $(a).data("ability");
const curRank = this.object.system.skills[skill].rank;
const availableXP = this.object.system.experience.available;
const careerSkill = this.object.system.skills[skill].careerskill;
const cost = careerSkill ? (curRank + 1) * 5 : (curRank + 1) * 5 + 5;

if (cost > availableXP) {
ui.notifications.warn(game.i18n.localize("SWFFG.Actors.Sheets.Purchase.NotEnoughXP"));
return;
}
const dialog = new Dialog(
{
title: game.i18n.localize("SWFFG.Actors.Sheets.Purchase.SkillRank.ConfirmTitle"),
content: game.i18n.format("SWFFG.Actors.Sheets.Purchase.SkillRank.Text", {cost: cost, skill: skill, old: curRank, new: curRank + 1}),
buttons: {
done: {
icon: '<i class="fas fa-dollar"></i>',
label: game.i18n.localize("SWFFG.Actors.Sheets.Purchase.ConfirmPurchase"),
callback: async (that) => {
// update the form because the fields are read when an update is performed
this.object.sheet.element.find(`[name="data.skills.${skill}.rank"]`).val(curRank + 1);
await this.object.sheet.submit({preventClose: true});
await this.object.update({
system: {
experience: {
available: availableXP - cost,
}
}
});
await xpLogSpend(game.actors.get(this.object.id), `skill rank ${skill} ${curRank} --> ${curRank + 1}`, cost);
},
},
cancel: {
icon: '<i class="fas fa-cancel"></i>',
label: game.i18n.localize("SWFFG.Actors.Sheets.Purchase.CancelPurchase"),
},
},
},
{
classes: ["dialog", "starwarsffg"],
}
).render(true);
}

/**
* Remove skill from skill list
* @param {object} a - Event object
Expand Down Expand Up @@ -1525,4 +1589,174 @@ export class ActorSheetFFG extends ActorSheet {

return cols;
}

async _buyCore(event) {
const action = $(event.target).data("buy-action");
const template = "systems/starwarsffg/templates/dialogs/ffg-confirm-purchase.html";
let content;
const availableXP = this.object.system.experience.available;
let itemType;
if (action === "specialization") {
const inCareer = this.object.items.find(i => i.type === "career").system.specializations;
const inCareerNames = Object.values(inCareer).map(i => i.name);
const sources = game.settings.get("starwarsffg", "specializationCompendiums").split(",");
let outCareer = [];
for (const source of sources) {
const pack = game.packs.get(source);
if (!pack) {
continue;
}
const items = await pack.getDocuments();
for (const item of items) {
if (!inCareerNames.includes(item.name)) {
outCareer.push({
name: item.name,
id: item.id,
source: item.uuid,
});
}
}
}
outCareer = sortDataBy(outCareer, "name");
const baseCost = (this.object.items.filter(i => i.type === "specialization").length + 1) * 10;
const increasedCost = baseCost + 10;
if (baseCost > availableXP) {
ui.notifications.warn(game.i18n.localize("SWFFG.Actors.Sheets.Purchase.NotEnoughXP"));
return;
} else if (increasedCost > availableXP) {
outCareer = [];
}
itemType = game.i18n.localize("TYPES.Item.specialization");
content = await renderTemplate(template, { inCareer, outCareer, baseCost, increasedCost, itemType: itemType });
} else if (action === "signatureability") {
const sources = game.settings.get("starwarsffg", "signatureAbilityCompendiums").split(",");
const rawSelectableItems = this.object.items.find(i => i.type === "career").system.signatureabilities;
const sigAbilityNames = Object.values(rawSelectableItems).map(i => i.name);
let selectableItems = [];
// pull items out of the world
for (const itemId of Object.keys(rawSelectableItems)) {
const item = rawSelectableItems[itemId];
let retrievedItem = game.items.get(item.id);
if (retrievedItem) {
selectableItems.push({
name: retrievedItem.name,
id: retrievedItem.id,
source: retrievedItem.uuid,
cost: parseInt(retrievedItem.system.base_cost),
});
}
}
// pull items out of compendiums
for (const source of sources) {
const pack = game.packs.get(source);
if (!pack) {
continue;
}
const items = await pack.getDocuments();
for (const item of items) {
if (sigAbilityNames.includes(item.name)) {
selectableItems.push({
name: item.name,
id: item.id,
source: item.uuid,
cost: parseInt(item.system.base_cost),
});
}
}
}
if (selectableItems.length === 0) {
ui.notifications.warn(game.i18n.localize("SWFFG.Actors.Sheets.Purchase.SA.NotSet"));
return;
}
selectableItems = sortDataBy(selectableItems, "name");
itemType = game.i18n.localize("TYPES.Item.signatureability");
content = await renderTemplate(template, { selectableItems, itemType: itemType });
} else if (action === "forcepower") {
const sources = game.settings.get("starwarsffg", "forcePowerCompendiums").split(",");
let selectableItems = [];
const worldItems = game.items.filter(i => i.type === "forcepower");
for (const worldItem of worldItems) {
selectableItems.push({
name: worldItem.name,
id: worldItem.id,
source: worldItem.uuid,
cost: worldItem.system.base_cost,
});
}
for (const source of sources) {
const pack = game.packs.get(source);
if (!pack) {
continue;
}
const items = await pack.getDocuments();
for (const item of items) {
selectableItems.push({
name: item.name,
id: item.id,
source: item.uuid,
cost: item.system.base_cost,
});
}
selectableItems = sortDataBy(selectableItems, "name");
itemType = game.i18n.localize("TYPES.Item.forcepower");
content = await renderTemplate(template, { selectableItems, itemType: itemType });
}
}

const dialog = new Dialog(
{
title: game.i18n.format("SWFFG.Actors.Sheets.Purchase.DialogTitle", {itemType: itemType}),
content: content,
buttons: {
done: {
icon: '<i class="fas fa-dollar"></i>',
label: game.i18n.localize("SWFFG.Actors.Sheets.Purchase.ConfirmPurchase"),
callback: async (that) => {
const cost = $("#ffgPurchase option:selected", that).data("cost");
const selected_id = $("#ffgPurchase option:selected", that).data("id");
const selected_source = $("#ffgPurchase option:selected", that).data("source");
let purchasedItem = game.items.get(selected_id);
if (!purchasedItem) {
purchasedItem = await fromUuid(selected_source);
}
await this.object.createEmbeddedDocuments("Item", [purchasedItem]);
await this.object.update({
system: {
experience: {
available: availableXP - cost,
},
},
});
await xpLogSpend(game.actors.get(this.object.id), `new ${action} ${purchasedItem.name}`, cost);
},
},
cancel: {
icon: '<i class="fas fa-cancel"></i>',
label: game.i18n.localize("SWFFG.Actors.Sheets.Purchase.CancelPurchase"),
},
},
},
{
classes: ["dialog", "starwarsffg"],
}
).render(true);
}
}

/**
* Sort an array of dicts by a key. Totally not AI generated. But it works :)
* @param data
* @param byKey
* @returns {*}
*/
function sortDataBy(data, byKey) {
return data.sort((a, b) => {
if (a[byKey] < b[byKey]) {
return -1;
}
if (a[byKey] > b[byKey]) {
return 1;
}
return 0;
});
}
5 changes: 4 additions & 1 deletion modules/groupmanager-ffg.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {xpLogEarn} from "./helpers/actor-helpers.js";

export class GroupManagerLayer extends CanvasLayer {
constructor() {
super();
Expand Down Expand Up @@ -342,11 +344,12 @@ export class GroupManager extends FormApplication {
one: {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("SWFFG.GrantXP"),
callback: () => {
callback: async () => {
const container = document.getElementById(id);
const amount = container.querySelector('input[name="amount"]');
character.update({ ["data.experience.total"]: +character.system.experience.total + +amount.value });
character.update({ ["data.experience.available"]: +character.system.experience.available + +amount.value });
await xpLogEarn(character, amount.value);
ui.notifications.info(`Granted ${amount.value} XP to ${character.name}.`);
},
},
Expand Down
27 changes: 27 additions & 0 deletions modules/helpers/actor-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,30 @@ export default class ActorHelpers {
return this.object.update(formData);
}
}

/**
* Adds a SPEND log entry to the actor's XP log (accessed via the notebook under specializations)
* @param actor - ffgActor object
* @param action - action taken (e.g. "skill rank Astrogation 1 --> 2")
* @param cost - XP spent
* @returns {Promise<void>}
*/
export async function xpLogSpend(actor, action, cost) {
const xpLog = actor.getFlag("starwarsffg", "xpLog") || [];
const date = new Date().toISOString().slice(0, 10);
let newEntry = `<font color="red"><b>${date}</b>: spent <b>${cost}</b> XP for <b>${action}</b></font>`;
await actor.setFlag("starwarsffg", "xpLog", [newEntry, ...xpLog]);
}

/**
* Adds a GRANT log entry to the actor's XP log (accessed via the notebook under specializations)
* @param actor - ffgActor object
* @param grant - XP granted
* @returns {Promise<void>}
*/
export async function xpLogEarn(actor, grant) {
const xpLog = actor.getFlag("starwarsffg", "xpLog") || [];
const date = new Date().toISOString().slice(0, 10);
let newEntry = `<font color="green"><b>${date}</b>: GM granted <b>${grant}</b> XP</font>`;
await actor.setFlag("starwarsffg", "xpLog", [newEntry, ...xpLog]);
}
Loading