fvtt-pegasus-rpg/modules/pegasus-actor.js

2144 lines
77 KiB
JavaScript

/* -------------------------------------------- */
import { PegasusUtility } from "./pegasus-utility.js";
import { PegasusRollDialog } from "./pegasus-roll-dialog.js";
/* -------------------------------------------- */
const coverBonusTable = { "nocover": 0, "lightcover": 2, "heavycover": 4, "entrenchedcover": 6 };
const statThreatLevel = ["agi", "str", "phy", "com", "def", "per"]
const __subkey2title = {
"melee-dmg": "Melee Damage", "melee-atk": "Melee Attack", "ranged-atk": "Ranged Attack",
"ranged-dmg": "Ranged Damage", "defence": "Defence", "dmg-res": "Damare Resistance"
}
const __statBuild = [
{ modules: ["vehiclehull"], field: "hr", itemfield: "hr" },
{ modules: ["vehiclehull", "vehiclemodule"], field: "hr", itemfield: "size", subfield: "size" },
//{ modules: ["vehiclehull"], field: "pc", itemfield: "vms", subfield: "avgnrg" },
//{ modules: ["powercoremodule"], field: "pc", itemfield: "nrg", subfield: "avgnrg" },
{ modules: ["vehiclehull", "mobilitymodule"], itemfield: "man", field: "man" },
{ modules: ["powercoremodule"], field: "pc", itemfield: "pc", additionnal1: "curnrg", additionnal2: "maxnrg" },
{ modules: ["mobilitymodule"], field: "mr", itemfield: "mr" },
{ modules: ["propulsionmodule"], field: "ad", itemfield: "ad" },
{ modules: ["combatmodule"], field: "fc", itemfield: "fc" },
]
const __isVehicleUnique = { vehiclehull: 1, powercoremodule: 1, mobilitymodule: 1, propulsionmodule: 1, combatmodule: 1 }
const __speed2Num = { fullstop: 0, crawling: 1, slow: 2, average: 3, fast: 4, extfast: 5 }
const __num2speed = ["fullstop", "crawling", "slow", "average", "fast", "extfast"]
const __isVehicle = { vehiclehull: 1, powercoremodule: 1, mobilitymodule: 1, combatmodule: 1, propulsionmodule: 1, vehiclemodule: 1, vehicleweaponmodule: 1, effect: 1 }
const __bonusEffect = {
name: "Crawling MAN Bonus", type: "effect", img: "systems/fvtt-pegasus-rpg/images/icons/icon_effect.webp",
system: {
type: "physical",
genre: "positive",
effectlevel: 3,
reducedicevalue: false,
stataffected: "man",
specaffected: [],
statdice: false,
bonusdice: true,
weapondamage: false,
hindrance: false,
resistedby: "notapplicable",
recoveryroll: false,
recoveryrollstat: "",
recoveryrollspec: [],
effectstatlevel: false,
effectstat: "",
oneuse: false,
ignorehealthpenalty: false,
isthispossible: "",
mentaldisruption: false,
physicaldisruption: false,
mentalimmunity: false,
physicalimmunity: false,
nobonusdice: false,
noperksallowed: false,
description: "",
otherdice: false
}
}
/* -------------------------------------------- */
/**
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class PegasusActor extends Actor {
/* -------------------------------------------- */
/**
* Override the create() function to provide additional SoS functionality.
*
* This overrided create() function adds initial items
* Namely: Basic skills, money,
*
* @param {Object} data Barebones actor data which this function adds onto.
* @param {Object} options (Unused) Additional options which customize the creation workflow.
*
*/
static async create(data, options) {
// Case of compendium global import
if (data instanceof Array) {
return super.create(data, options);
}
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
if (data.items) {
let actor = super.create(data, options);
return actor;
}
if (data.type == 'character') {
}
if (data.type == 'npc') {
}
return super.create(data, options);
}
/* -------------------------------------------- */
prepareBaseData() {
}
/* -------------------------------------------- */
async prepareData() {
super.prepareData();
}
/* -------------------------------------------- */
prepareDerivedData() {
if (!this.traumaState) {
this.traumaState = "none"
}
if (this.type == 'character') {
this.computeNRGHealth();
this.system.encCapacity = this.getEncumbranceCapacity()
this.buildContainerTree()
}
if (this.type == 'vehicle') {
this.computeVehicleStats();
}
super.prepareDerivedData();
}
/* -------------------------------------------- */
_preUpdate(changed, options, user) {
super._preUpdate(changed, options, user);
}
/* -------------------------------------------- */
getEncumbranceCapacity() {
return this.system.statistics.str.value * 25
}
/* -------------------------------------------- */
getActivePerks() {
let perks = this.items.filter(item => item.type == 'perk' && item.system.active);
return perks;
}
/* -------------------------------------------- */
getAbilities() {
let ab = this.items.filter(item => item.type == 'ability');
return ab;
}
/* -------------------------------------------- */
getPerks() {
let comp = this.items.filter(item => item.type == 'perk');
return comp;
}
/* -------------------------------------------- */
getEffects() {
let comp = this.items.filter(item => item.type == 'effect');
return comp;
}
/* -------------------------------------------- */
getCombatModules() {
let comp = this.items.filter(item => item.type == 'combatmodule');
return comp;
}
/* -------------------------------------------- */
getVehicleHull() {
let comp = this.items.filter(item => item.type == 'vehiclehull');
return comp;
}
/* -------------------------------------------- */
getPowercoreModules() {
let comp = this.items.filter(item => item.type == 'powercoremodule');
return comp;
}
/* -------------------------------------------- */
getMobilityModules() {
let comp = this.items.filter(item => item.type == 'mobilitymodule');
return comp;
}
/* -------------------------------------------- */
getPropulsionModules() {
let comp = this.items.filter(item => item.type == 'propulsionmodule');
return comp;
}
/* -------------------------------------------- */
getVehicleModules() {
let comp = this.items.filter(item => item.type == 'vehiclemodule');
return comp;
}
/* -------------------------------------------- */
getVehicleWeaponModules() {
let comp = this.items.filter(item => item.type == 'vehicleweaponmodule');
return comp;
}
/* -------------------------------------------- */
getPowers() {
let comp = this.items.filter(item => item.type == 'power');
return comp;
}
/* -------------------------------------------- */
getMoneys() {
let comp = this.items.filter(item => item.type == 'money');
return comp;
}
/* -------------------------------------------- */
getVirtues() {
let comp = this.items.filter(item => item.type == 'virtue');
return comp;
}
/* -------------------------------------------- */
getVices() {
let comp = this.items.filter(item => item.type == 'vice');
return comp;
}
/* -------------------------------------------- */
getArmors() {
let comp = duplicate(this.items.filter(item => item.type == 'armor') || []);
return comp;
}
/* -------------------------------------------- */
getShields() {
let comp = this.items.filter(item => item.type == 'shield')
return comp;
}
getRace() {
let race = this.items.filter(item => item.type == 'race')
return race[0] ?? [];
}
getRole() {
let role = this.items.filter(item => item.type == 'role')
return role[0] ?? [];
}
/* -------------------------------------------- */
checkAndPrepareEquipment(item) {
if (item.system.resistance) {
item.system.resistanceDice = PegasusUtility.getDiceFromLevel(item.system.resistance)
}
if (item.system.idr) {
item.system.idrDice = PegasusUtility.getDiceFromLevel(item.system.idr)
}
if (item.system.damage) {
item.system.damageDice = PegasusUtility.getDiceFromLevel(item.system.damage)
}
if (item.system.level) {
item.system.levelDice = PegasusUtility.getDiceFromLevel(item.system.level)
}
}
/* -------------------------------------------- */
checkAndPrepareEquipments(listItem) {
for (let item of listItem) {
this.checkAndPrepareEquipment(item)
}
return listItem
}
/* -------------------------------------------- */
getWeapons() {
let comp = duplicate(this.items.filter(item => item.type == 'weapon') || []);
return comp;
}
/* -------------------------------------------- */
getItemById(id) {
let item = this.items.find(item => item.id == id);
if (item) {
item = duplicate(item)
if (item.type == 'specialisation') {
item.system.dice = PegasusUtility.getDiceFromLevel(item.system.level);
}
}
return item;
}
/* -------------------------------------------- */
getSpecs() {
let comp = duplicate(this.items.filter(item => item.type == 'specialisation') || []);
for (let c of comp) {
c.system.dice = PegasusUtility.getDiceFromLevel(c.system.level);
}
return comp;
}
/* -------------------------------------------- */
async manageWorstFear(flag) {
if (flag) {
let effect = await PegasusUtility.getEffectFromCompendium("Worst Fear")
effect.system.worstfear = true
this.createEmbeddedDocuments('Item', [effect])
} else {
let effect = this.items.find(item => item.type == "effect" && item.system.worstfear)
if (effect) {
this.deleteEmbeddedDocuments('Item', [effect.id])
}
}
}
/* -------------------------------------------- */
async manageDesires(flag) {
if (flag) {
let effect = await PegasusUtility.getEffectFromCompendium("Desire")
//console.log("EFFECT", effect)
effect.system.desires = true
this.createEmbeddedDocuments('Item', [effect])
} else {
let effect = this.items.find(item => item.type == "effect" && item.system.desires)
if (effect) {
this.deleteEmbeddedDocuments('Item', [effect.id])
}
}
}
/* -------------------------------------------- */
getRelevantSpec(statKey) {
let comp = duplicate(this.items.filter(item => item.type == 'specialisation' && item.system.statistic == statKey) || []);
for (let c of comp) {
c.system.dice = PegasusUtility.getDiceFromLevel(c.system.level);
}
return comp;
}
/* -------------------------------------------- */
async activatePerk(perkId) {
let item = this.items.find(item => item.id == perkId);
if (item && item.system) {
let update = { _id: item.id, "data.active": !item.system.active };
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
async activateViceOrVirtue(itemId) {
let item = this.items.find(item => item.id == itemId)
if (item && item.system) {
let nrg = duplicate(this.system.nrg)
if (!item.system.activated) { // Current value
let effects = []
for (let effect of item.system.effectsgained) {
effect.system.powerId = itemId // Link to the perk, in order to dynamically remove them
effects.push(effect)
}
if (effects.length) {
await this.createEmbeddedDocuments('Item', effects)
}
} else {
let toRem = []
for (let item of this.items) {
if (item.type == 'effect' && item.system.powerId == itemId) {
toRem.push(item.id)
}
}
if (toRem.length) {
await this.deleteEmbeddedDocuments('Item', toRem)
}
}
let update = { _id: item.id, "data.activated": !item.system.activated }
await this.updateEmbeddedDocuments('Item', [update]) // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
async activatePower(itemId) {
let item = this.items.find(item => item.id == itemId)
if (item && item.system) {
let nrg = duplicate(this.system.nrg)
if (!item.system.activated) { // Current value
if (item.system.costspent > nrg.value || item.system.costspent > nrg.max) {
return ui.notifications.warn("Not enough NRG to activate the Power " + item.name)
}
nrg.activated += item.system.costspent
nrg.value -= item.system.costspent
nrg.max -= item.system.costspent
await this.update({ 'system.nrg': nrg })
let effects = []
for (let effect of item.system.effectsgained) {
effect.system.powerId = itemId // Link to the perk, in order to dynamically remove them
effects.push(effect)
}
if (effects.length) {
await this.createEmbeddedDocuments('Item', effects)
}
if (item.system.activatedtext.length > 0) {
ChatMessage.create({ content: `Power ${item.name} activated : ${item.system.activatedtext}` })
}
} else {
nrg.activated -= item.system.costspent
nrg.max += item.system.costspent
await this.update({ 'system.nrg': nrg })
let toRem = []
for (let item of this.items) {
if (item.type == 'effect' && item.system.powerId == itemId) {
toRem.push(item.id)
}
}
if (toRem.length) {
await this.deleteEmbeddedDocuments('Item', toRem)
}
if (item.system.deactivatedtext.length > 0) {
ChatMessage.create({ content: `Power ${item.name} deactivated : ${item.system.deactivatedtext}` })
}
}
let update = { _id: item.id, "data.activated": !item.system.activated }
await this.updateEmbeddedDocuments('Item', [update]) // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
async equipItem(itemId) {
let item = this.items.find(item => item.id == itemId);
if (item && item.system) {
let update = { _id: item.id, "data.equipped": !item.system.equipped };
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
compareName(a, b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
}
/* ------------------------------------------- */
getEquipments() {
return this.items.filter(item => item.type == 'shield' || item.type == 'armor' || item.type == "weapon" || item.type == "equipment");
}
/* ------------------------------------------- */
getEquipmentsOnly() {
return duplicate(this.items.filter(item => item.type == "equipment") || [])
}
/* ------------------------------------------- */
computeThreatLevel() {
let tl = 0
for (let key of statThreatLevel) { // Init with concerned stats
tl += PegasusUtility.getDiceValue(this.system.statistics[key].value)
}
let powers = duplicate(this.getPowers() || [])
if (powers.length > 0) { // Then add some mental ones of powers
tl += PegasusUtility.getDiceValue(this.system.statistics.foc.value)
tl += PegasusUtility.getDiceValue(this.system.statistics.mnd.value)
}
tl += PegasusUtility.getDiceValue(this.system.mr.value)
let specThreat = this.items.filter(it => it.type == "specialisation" && it.system.isthreatlevel) || []
for (let spec of specThreat) {
tl += PegasusUtility.getDiceValue(spec.system.level)
}
tl += this.system.nrg.absolutemax + this.system.secondary.health.max + this.system.secondary.delirium.max
tl += this.getPerks().length * 5
let weapons = this.getWeapons()
for (let weapon of weapons) {
tl += PegasusUtility.getDiceValue(weapon.system.damage)
}
let armors = this.getArmors()
for (let armor of armors) {
tl += PegasusUtility.getDiceValue(armor.system.resistance)
}
let shields = duplicate(this.getShields())
for (let shield of shields) {
tl += PegasusUtility.getDiceValue(shield.system.level)
}
let abilities = duplicate(this.getAbilities())
for (let ability of abilities) {
tl += ability.system.threatlevel
}
let equipments = this.getEquipmentsOnly()
for (let equip of equipments) {
tl += equip.system.threatlevel
}
if (tl != this.system.biodata.threatlevel) {
this.update({ 'system.biodata.threatlevel': tl })
}
}
/* ------------------------------------------- */
async buildContainerTree() {
let equipments = duplicate(this.items.filter(item => item.type == "equipment") || [])
for (let equip1 of equipments) {
if (equip1.system.iscontainer) {
equip1.system.contents = []
equip1.system.contentsEnc = 0
for (let equip2 of equipments) {
if (equip1._id != equip2._id && equip2.system.containerid == equip1._id) {
equip1.system.contents.push(equip2)
let q = equip2.system.quantity ?? 1
equip1.system.contentsEnc += q * equip2.system.weight
}
}
}
}
// Compute whole enc
let enc = 0
for (let item of equipments) {
item.system.idrDice = PegasusUtility.getDiceFromLevel(Number(item.system.idr))
if (item.system.equipped) {
if (item.system.iscontainer) {
enc += item.system.contentsEnc
} else if (item.system.containerid == "") {
let q = item.system.quantity ?? 1
enc += q * item.system.weight
}
}
}
for (let item of this.items) { // Process items/shields/armors
if ((item.type == "weapon" || item.type == "shield" || item.type == "armor") && item.system.equipped) {
let q = item.system.quantity ?? 1
enc += q * item.system.weight
}
}
// Store local values
this.encCurrent = enc
this.containersTree = equipments.filter(item => item.system.containerid == "") // Returns the root of equipements without container
// Manages slow effect
let overCapacity = Math.floor(this.encCurrent / this.getEncumbranceCapacity())
this.encHindrance = Math.floor(this.encCurrent / this.getEncumbranceCapacity())
//console.log("Capacity", overCapacity, this.encCurrent / this.getEncumbranceCapacity() )
let effect = this.items.find(item => item.type == "effect" && item.system.slow)
if (overCapacity >= 4) {
if (!effect) {
effect = await PegasusUtility.getEffectFromCompendium("Slowed")
effect.system.slow = true
this.createEmbeddedDocuments('Item', [effect])
}
} else {
if (effect) {
this.deleteEmbeddedDocuments('Item', [effect.id])
}
}
}
/* -------------------------------------------- */
modifyStun(incDec) {
let combat = duplicate(this.system.combat)
combat.stunlevel += incDec
if (combat.stunlevel >= 0) {
this.update({ 'system.combat': combat })
let chatData = {
user: game.user.id,
rollMode: game.settings.get("core", "rollMode"),
whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM'))
}
if (incDec > 0) {
chatData.content = `<div>${this.name} suffered a Stun level.</div`
} else {
chatData.content = `<div>${this.name} recovered a Stun level.</div`
}
ChatMessage.create(chatData)
} else {
ui.notifications.warn("Stun level cannot go below 0")
}
let stunAbove = combat.stunlevel - combat.stunthreshold
if (stunAbove > 0) {
ChatMessage.create({ content: `${this.name} Stun threshold has been exceeded.` })
}
if (incDec > 0 && stunAbove > 0) {
let delirium = duplicate(this.system.secondary.delirium)
delirium.value -= incDec
this.update({ 'system.secondary.delirium': delirium })
}
}
/* -------------------------------------------- */
modifyMomentum(incDec) {
let momentum = duplicate(this.system.momentum)
momentum.value += incDec
this.update({ 'system.momentum': momentum })
let chatData = {
user: game.user.id,
rollMode: game.settings.get("core", "rollMode"),
whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM'))
}
if (incDec > 0) {
chatData.content = `<div>${this.name} has gained a Momentum</div`
} else {
chatData.content = `<div>${this.name} has used a Momentum</div`
}
ChatMessage.create(chatData)
if (incDec < 0 && momentum.value >= 0) {
PegasusUtility.showMomentumDialog(this.id)
}
}
/* -------------------------------------------- */
getActiveEffects(matching = it => true) {
let array = Array.from(this.getEmbeddedCollection("ActiveEffect").values());
return Array.from(this.getEmbeddedCollection("ActiveEffect").values()).filter(it => matching(it));
}
/* -------------------------------------------- */
getEffectByLabel(label) {
return this.getActiveEffects().find(it => it.system.label == label);
}
/* -------------------------------------------- */
getEffectById(id) {
return this.getActiveEffects().find(it => it.id == id);
}
/* -------------------------------------------- */
getAttribute(attrKey) {
return this.system.attributes[attrKey];
}
/* -------------------------------------------- */
async addObjectToContainer(itemId, containerId) {
let container = this.items.find(item => item.id == containerId && item.system.iscontainer)
let object = this.items.find(item => item.id == itemId)
if (container) {
if (object.system.iscontainer) {
ui.notifications.warn("Only 1 level of container allowed")
return
}
let alreadyInside = this.items.filter(item => item.system.containerid && item.system.containerid == containerId);
if (alreadyInside.length >= container.system.containercapacity) {
ui.notifications.warn("Container is already full !")
return
} else {
await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'system.containerid': containerId }])
}
} else if (object && object.system.containerid) { // remove from container
console.log("Removeing: ", object)
await this.updateEmbeddedDocuments("Item", [{ _id: object.id, 'system.containerid': "" }]);
}
}
/* -------------------------------------------- */
checkVirtue(virtue) {
let vices = this.getVices()
for (let vice of vices) {
let nonVirtues = vice.system.unavailablevirtue
for (let blockedVirtue of nonVirtues) {
if (blockedVirtue.name.toLowerCase() == virtue.name.toLowerCase()) {
return false
}
}
}
return true
}
/* -------------------------------------------- */
checkVice(vice) {
let virtues = this.getVirtues()
for (let virtue of virtues) {
let nonVices = virtue.system.unavailablevice
for (let blockedVice of nonVices) {
if (blockedVice.name.toLowerCase() == vice.name.toLowerCase()) {
return false
}
}
}
return true
}
/* -------------------------------------------- */
async preprocessItem(event, item, onDrop = false) {
console.log("Pre-process", item)
if (item.type != "effect" && __isVehicle[item.type]) {
ui.notifications.warn("You can't drop Vehicles item over a character sheet.")
return
}
// Pre-filter effects
if (item.type == 'effect') {
if (this.checkMentalDisruption() && item.system.type == "mental" && item.system.genre == "positive") {
ChatMessage.create({ content: "Effects of this type cannot be applied while Disruption is applied, Use a Soft Action to remove Disruption" })
return
}
if (this.checkPhysicalDisruption() && item.system.type == "physical" && item.system.genre == "positive") {
ChatMessage.create({ content: "Effects of this type cannot be applied while Disruption is applied, Use a Soft Action to remove Disruption" })
return
}
if (this.checkMentalImmunity() && item.system.type == "mental" && item.system.genre == "negative") {
ChatMessage.create({ content: "Effects of this type cannot be applied while Immunity is applied" })
return
}
if (this.checkPhysicalImmunity() && item.system.type == "physical" && item.system.genre == "negative") {
ChatMessage.create({ content: "Effects of this type cannot be applied while Immunity is applied" })
return
}
}
if (item.type == 'race') {
this.applyRace(item)
} else if (item.type == 'role') {
this.applyRole(item)
} else if (item.type == 'ability') {
this.applyAbility(item, [], true)
if (!onDrop) {
await this.createEmbeddedDocuments('Item', [item])
}
} else {
if (!onDrop) {
await this.createEmbeddedDocuments('Item', [item])
}
}
// Check virtue/vice validity
if (item.type == "virtue") {
if (!this.checkVirtue(item)) {
ui.notifications.info("Virtue is not allowed due to Vice.")
return false
}
}
if (item.type == "vice") {
if (!this.checkVice(item)) {
ui.notifications.info("Vice is not allowed due to Virtue.")
return false
}
}
if (item.type == "power" && item.system.purchasedtext.length > 0) {
ChatMessage.create({ content: `Power ${item.name} puchased : ${item.system.purchasedtext}` })
}
let dropID = $(event.target).parents(".item").attr("data-item-id") // Only relevant if container drop
let objectID = item.id || item._id
this.addObjectToContainer(objectID, dropID)
return true
}
/* -------------------------------------------- */
async equipGear(equipmentId) {
let item = this.items.find(item => item.id == equipmentId);
if (item && item.system) {
let update = { _id: item.id, "data.equipped": !item.system.equipped };
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
getInitiativeScore(combatId, combatantId) {
if (this.type == 'character') {
this.rollMR(true, combatId, combatantId)
}
console.log("Init required !!!!")
return -1;
}
/* -------------------------------------------- */
getSubActors() {
let subActors = [];
for (let id of this.system.subactors) {
subActors.push(duplicate(game.actors.get(id)))
}
return subActors;
}
/* -------------------------------------------- */
async addSubActor(subActorId) {
let subActors = duplicate(this.system.subactors);
subActors.push(subActorId);
await this.update({ 'system.subactors': subActors });
}
/* -------------------------------------------- */
async delSubActor(subActorId) {
let newArray = [];
for (let id of this.system.subactors) {
if (id != subActorId) {
newArray.push(id);
}
}
await this.update({ 'system.subactors': newArray });
}
/* -------------------------------------------- */
syncRoll(rollData) {
let linkedRollId = PegasusUtility.getDefenseState(this.id);
if (linkedRollId) {
rollData.linkedRollId = linkedRollId;
}
this.lastRollId = rollData.rollId;
PegasusUtility.saveRollData(rollData);
}
/* -------------------------------------------- */
getStat(statKey) {
let stat
if (this.type == "character" && statKey == 'mr') {
stat = duplicate(this.system.mr);
} else {
stat = duplicate(this.system.statistics[statKey]);
}
stat.dice = PegasusUtility.getDiceFromLevel(stat.value || stat.level);
return stat;
}
/* -------------------------------------------- */
getOneSpec(specId) {
let spec = this.items.find(item => item.type == 'specialisation' && item.id == specId)
if (spec) {
spec = duplicate(spec);
spec.system.dice = PegasusUtility.getDiceFromLevel(spec.system.level);
}
return spec;
}
/* -------------------------------------------- */
specPowerActivate(specId) {
let spec = this.getOneSpec(specId)
if (spec) {
let powers = []
for (let power of spec.system.powers) {
if (power.data) {
power.system = power.data
}
power.system.specId = specId
powers.push(power)
}
if (powers.length > 0) {
this.createEmbeddedDocuments('Item', powers)
}
this.updateEmbeddedDocuments('Item', [{ _id: specId, 'system.powersactivated': true }])
}
}
/* -------------------------------------------- */
specPowerDeactivate(specId) {
let toRem = []
for (let power of this.items) {
if (power.type == "power" && power.system.specId && power.system.specId == specId) {
toRem.push(power.id)
}
}
if (toRem.length > 0) {
this.deleteEmbeddedDocuments('Item', toRem)
}
this.updateEmbeddedDocuments('Item', [{ _id: specId, 'system.powersactivated': false }])
}
/* -------------------------------------------- */
equipActivate(itemId) {
let item = this.items.get(itemId)
if (item) {
let effects = []
for (let effect of item.system.effects) {
effect.system.itemId = itemId // Keep link
effects.push(effect)
}
if (effects.length > 0) {
this.createEmbeddedDocuments('Item', effects)
}
this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'system.activated': true }])
}
}
/* -------------------------------------------- */
equipDeactivate(itemId) {
let toRem = []
for (let item of this.items) {
if (item.system.itemId && item.system.itemId == itemId) {
toRem.push(item.id)
}
}
if (toRem.length > 0) {
this.deleteEmbeddedDocuments('Item', toRem)
}
this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'system.activated': false }])
}
/* -------------------------------------------- */
async perkEffectUsed(itemId) {
let effect = this.items.get(itemId)
if (effect) {
PegasusUtility.createChatWithRollMode(effect.name, {
content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-effect-used.html`, effect.data)
});
this.deleteEmbeddedDocuments('Item', [effect.id])
}
}
/* -------------------------------------------- */
disableWeaverPerk(perk) {
if (perk.system.isweaver) {
for (let spec of this.items) {
if (spec.type == 'specialisation' && spec.system.ispowergroup) {
this.specPowerDeactivate(spec.id)
}
}
}
}
/* -------------------------------------------- */
enableWeaverPerk(perk) {
if (perk.system.isweaver) {
for (let spec of this.items) {
if (spec.type == 'specialisation' && spec.system.ispowergroup) {
this.specPowerActivate(spec.id)
}
}
}
}
/* -------------------------------------------- */
async cleanPerkEffects(itemId) {
let effects = []
for (let item of this.items) {
if (item.type == "effect" && item.system.perkId == itemId) {
effects.push(item.id)
}
}
if (effects.length > 0) {
console.log("DELET!!!!", effects, this)
await this.deleteEmbeddedDocuments('Item', effects)
}
}
/* -------------------------------------------- */
async updatePerkUsed(itemId, index, checked) {
let item = this.items.get(itemId)
if (item && index) {
let key = "data.used" + index
await this.updateEmbeddedDocuments('Item', [{ _id: itemId, [`${key}`]: checked }])
item = this.items.get(itemId) // Refresh
if (item.system.nbuse == "next1action" && item.system.used1) {
this.cleanPerkEffects(itemId)
}
if (item.system.nbuse == "next2action" && item.system.used1 && item.system.used2) {
this.cleanPerkEffects(itemId)
}
if (item.system.nbuse == "next3action" && item.system.used1 && item.system.used2 && item.system.used3) {
this.cleanPerkEffects(itemId)
}
}
}
/* -------------------------------------------- */
async updatePowerSpentCost(itemId, value) {
let item = this.items.get(itemId)
if (item && value) {
value = Number(value) || 0
await this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'system.costspent': value }])
}
}
/* -------------------------------------------- */
async cleanupPerksIfTrauma() {
if (this.getTraumaState() == "severetrauma") {
for (let perk of this.items) {
if (perk.type == "perk") {
this.cleanPerkEffects(perk.id)
this.updateEmbeddedDocuments('Item', [{ _id: perk.id, 'system.status': "ready", 'system.used1': false, 'system.used2': false, 'system.used3': false }])
ChatMessage.create({ content: `Perk ${perk.name} has been deactivated due to Severe Trauma state !` })
}
}
}
}
/* -------------------------------------------- */
incDecNRG(value) {
if (this.type == "character") {
let nrg = duplicate(this.system.nrg)
nrg.value += value
if (nrg.value >= 0 && nrg.value <= nrg.max) {
this.update({ 'system.nrg': nrg })
}
} else {
let pc = duplicate(this.system.statistics.pc)
pc.curnrg += value
if (pc.curnrg >= 0 && pc.curnrg <= pc.maxnrg) {
this.update({ 'system.statistics.pc': pc })
}
}
}
/* -------------------------------------------- */
async updatePerkStatus(itemId, status) {
let item = this.items.get(itemId)
if (item) {
if (item.system.status == status) return;// Ensure we are really changing the status
if (this.checkNoPerksAllowed()) {
await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.status': "ready" }])
ChatMessage.create({ content: "No perks activation allowed due to effect !" })
return
}
// Severe Trauma management
if (this.getTraumaState() == "severetrauma") {
if (!this.severeTraumaMessage) {
let chatData = {
user: game.user.id,
rollMode: game.settings.get("core", "rollMode"),
whisper: [game.user.id].concat(ChatMessage.getWhisperRecipients('GM'))
}
chatData.content = `<div>${this.name} is suffering from Severe Trauma and cannot use Perks at this time.</div`
ChatMessage.create(chatData)
this.severeTraumaMessage = true
}
this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'system.status': "ready", 'system.used1': false, 'system.used2': false, 'system.used3': false }])
return
}
let updateOK = true
if (status == "ready") {
await this.cleanPerkEffects(itemId)
await this.updateEmbeddedDocuments('Item', [{ _id: itemId, 'system.used1': false, 'system.used2': false, 'system.used3': false }])
if (item.system.features.nrgcost.flag) {
let nrg = duplicate(this.system.nrg)
nrg.activated -= item.system.features.nrgcost.value
nrg.max += item.system.features.nrgcost.value
await this.update({ 'system.nrg': nrg })
}
if (item.system.features.bonushealth.flag) {
let health = duplicate(this.system.secondary.health)
health.value -= Number(item.system.features.bonushealth.value) || 0
health.max -= Number(item.system.features.bonushealth.value) || 0
await this.update({ 'system.secondary.health': health })
}
if (item.system.features.bonusdelirium.flag) {
let delirium = duplicate(this.system.secondary.delirium)
delirium.value -= Number(item.system.features.bonusdelirium.value) || 0
delirium.max -= Number(item.system.features.bonusdelirium.value) || 0
await this.update({ 'system.secondary.delirium': delirium })
}
if (item.system.features.bonusnrg.flag) {
let nrg = duplicate(this.system.nrg)
nrg.value -= Number(item.system.features.bonusnrg.value) || 0
nrg.max -= Number(item.system.features.bonusnrg.value) || 0
await this.update({ 'system.nrg': nrg })
}
this.disableWeaverPerk(item)
PegasusUtility.createChatWithRollMode(item.name, {
content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-perk-ready.html`, { name: this.name, perk: duplicate(item) })
})
}
if (status == "activated") {
// Add effects linked to the perk
let effects = []
for (let effect of item.system.effectsgained) {
if (effect.data) {
effect.system = effect.data
}
effect.system.perkId = itemId // Link to the perk, in order to dynamically remove them
effect.system.isUsed = false // Flag to indicate removal when used in a roll window
effects.push(effect)
}
if (effects.length) {
await this.createEmbeddedDocuments('Item', effects)
}
// Manage additional flags
if (item.system.features.nrgcost.flag) {
if ((this.system.nrg.value >= item.system.features.nrgcost.value) && (this.system.nrg.max >= item.system.features.nrgcost.value)) {
let nrg = duplicate(this.system.nrg)
nrg.activated += item.system.features.nrgcost.value
nrg.value -= item.system.features.nrgcost.value
nrg.max -= item.system.features.nrgcost.value
await this.update({ 'system.nrg': nrg })
} else {
updateOK = false
ui.notifications.warn("Not enough NRG to activate the Perk " + item.name)
}
}
if (item.system.features.bonushealth.flag) {
let health = duplicate(this.system.secondary.health)
health.value += Number(item.system.features.bonushealth.value) || 0
health.max += Number(item.system.features.bonushealth.value) || 0
await this.update({ 'system.secondary.health': health })
}
if (item.system.features.bonusdelirium.flag) {
let delirium = duplicate(this.system.secondary.delirium)
delirium.value += Number(item.system.features.bonusdelirium.value) || 0
delirium.max += Number(item.system.features.bonusdelirium.value) || 0
await this.update({ 'system.secondary.delirium': delirium })
}
if (item.system.features.bonusnrg.flag) {
let nrg = duplicate(this.system.nrg)
nrg.value += Number(item.system.features.bonusnrg.value) || 0
nrg.max += Number(item.system.features.bonusnrg.value) || 0
await this.update({ 'system.nrg': nrg })
}
PegasusUtility.createChatWithRollMode(item.name, {
content: await renderTemplate(`systems/fvtt-pegasus-rpg/templates/chat-perk-activated.html`, { name: this.name, perk: duplicate(item) })
})
this.enableWeaverPerk(item)
}
if (updateOK) {
await this.updateEmbeddedDocuments('Item', [{ _id: item.id, 'system.status': status }])
}
}
}
/* -------------------------------------------- */
async deleteAllItemsByType(itemType) {
let items = this.items.filter(item => item.type == itemType);
await this.deleteEmbeddedDocuments('Item', items);
}
/* -------------------------------------------- */
async addItemWithoutDuplicate(newItem) {
let item = this.items.find(item => item.type == newItem.type && item.name.toLowerCase() == newItem.name.toLowerCase())
if (!item) {
await this.createEmbeddedDocuments('Item', [newItem]);
}
}
/* -------------------------------------------- */
getTraumaState() {
this.traumaState = "none"
if (this.type == "character") {
let negDelirium = -Math.floor((this.system.secondary.delirium.max + 1) / 2)
if (this.type == "character") {
if (this.system.secondary.delirium.value <= 0 && this.system.secondary.delirium.value >= negDelirium) {
this.traumaState = "trauma"
}
if (this.system.secondary.delirium.value < negDelirium) {
this.traumaState = "severetrauma"
}
}
}
return this.traumaState
}
/* -------------------------------------------- */
getLevelRemaining() {
return this.system.biodata.currentlevelremaining
}
/* -------------------------------------------- */
modifyHeroLevelRemaining(incDec) {
let biodata = duplicate(this.system.biodata)
biodata.currentlevelremaining = Math.max(biodata.currentlevelremaining + incDec, 0)
this.update({ "data.biodata": biodata })
ChatMessage.create({ content: `${this.name} has used a Hero Level to reroll !` })
return biodata.currentlevelremaining
}
/* -------------------------------------------- */
checkIgnoreHealth() {
for (let effect of this.items) {
if (effect.type == "effect" && effect.system.ignorehealthpenalty) {
return true
}
}
return false
}
/* -------------------------------------------- */
checkMentalDisruption() {
for (let effect of this.items) {
if (effect.type == "effect" && effect.system.mentaldisruption) {
return true
}
}
return false
}
/* -------------------------------------------- */
checkPhysicalDisruption() {
for (let effect of this.items) {
if (effect.type == "effect" && effect.system.physicaldisruption) {
return true
}
}
return false
}
/* -------------------------------------------- */
checkMentalImmunity() {
for (let effect of this.items) {
if (effect.type == "effect" && effect.system.mentalimmunity) {
return true
}
}
return false
}
/* -------------------------------------------- */
checkPhysicalImmunity() {
for (let effect of this.items) {
if (effect.type == "effect" && effect.system.physicalimmunity) {
return true
}
}
return false
}
/* -------------------------------------------- */
checkNoBonusDice() {
for (let effect of this.items) {
if (effect.type == "effect" && effect.system.nobonusdice) {
return true
}
}
return false
}
/* -------------------------------------------- */
checkNoPerksAllowed() {
for (let effect of this.items) {
if (effect.type == "effect" && effect.system.noperksallowed) {
return true
}
}
return false
}
/* -------------------------------------------- */
checkIfPossible() {
for (let effect of this.items) {
if (effect.type == "effect" && effect.system.isthispossible.length > 0) {
ChatMessage.create({ content: effect.system.isthispossible })
}
}
}
/* -------------------------------------------- */
async computeNRGHealth() {
if (this.isOwner || game.user.isGM) {
let updates = {}
let phyDiceValue = PegasusUtility.getDiceValue(this.system.statistics.phy.value) + this.system.secondary.health.bonus + this.system.statistics.phy.mod;
if (phyDiceValue != this.system.secondary.health.max) {
updates['system.secondary.health.max'] = phyDiceValue
}
if (this.computeValue) {
updates['system.secondary.health.value'] = phyDiceValue
}
let mndDiceValue = PegasusUtility.getDiceValue(this.system.statistics.mnd.value) + this.system.secondary.delirium.bonus + this.system.statistics.mnd.mod;
if (mndDiceValue != this.system.secondary.delirium.max) {
updates['system.secondary.delirium.max'] = mndDiceValue
}
if (this.computeValue) {
updates['system.secondary.delirium.value'] = mndDiceValue
}
let stlDiceValue = PegasusUtility.getDiceValue(this.system.statistics.stl.value) + this.system.secondary.stealthhealth.bonus + this.system.statistics.stl.mod;
if (stlDiceValue != this.system.secondary.stealthhealth.max) {
updates['system.secondary.stealthhealth.max'] = stlDiceValue
}
if (this.computeValue) {
updates['system.secondary.stealthhealth.value'] = stlDiceValue
}
let socDiceValue = PegasusUtility.getDiceValue(this.system.statistics.soc.value) + this.system.secondary.socialhealth.bonus + this.system.statistics.soc.mod;
if (socDiceValue != this.system.secondary.socialhealth.max) {
updates['system.secondary.socialhealth.max'] = socDiceValue
}
if (this.computeValue) {
updates['system.secondary.socialhealth.value'] = socDiceValue
}
let nrgValue = PegasusUtility.getDiceValue(this.system.statistics.foc.value) + this.system.nrg.mod + this.system.statistics.foc.mod
if (nrgValue != this.system.nrg.absolutemax) {
updates['system.nrg.absolutemax'] = nrgValue
}
if (this.computeValue) {
updates['system.nrg.max'] = nrgValue
updates['system.nrg.value'] = nrgValue
}
let stunth = PegasusUtility.getDiceValue(this.system.statistics.phy.value) + PegasusUtility.getDiceValue(this.system.statistics.mnd.value) + PegasusUtility.getDiceValue(this.system.statistics.foc.value)
+ this.system.statistics.mnd.mod + this.system.statistics.phy.mod + this.system.statistics.foc.mod
if (stunth != this.system.combat.stunthreshold) {
updates['system.combat.stunthreshold'] = stunth
}
let momentum = this.system.statistics.foc.value + this.system.statistics.foc.mod
if (momentum != this.system.momentum.max) {
updates['system.momentum.value'] = 0
updates['system.momentum.max'] = momentum
}
let mrLevel = (this.system.statistics.agi.value + this.system.statistics.str.value) - this.system.statistics.phy.value
mrLevel = (mrLevel < 1) ? 1 : mrLevel;
if (mrLevel != this.system.mr.value) {
updates['system.mr.value'] = mrLevel
}
let moralitythreshold = - (Number(PegasusUtility.getDiceValue(this.system.statistics.foc.value)) + Number(this.system.statistics.foc.mod))
if (moralitythreshold != this.system.biodata.moralitythreshold) {
updates['system.biodata.moralitythreshold'] = moralitythreshold
}
if (!this.isToken) {
if (this.warnMorality != this.system.biodata.morality && this.system.biodata.morality < 0) {
console.log("CHAR", this)
ChatMessage.create({ content: "WARNING: Your character is dangerously close to becoming corrupted and defeated. Start on a path of redemption!" })
}
if (this.warnMorality != this.system.biodata.morality) {
this.warnMorality = this.system.biodata.morality
}
}
let race = this.getRace()
if (race && race.name && (race.name != this.system.biodata.racename)) {
updates['system.biodata.racename'] = race.name
}
let role = this.getRole()
if (role && role.name && (role.name != this.system.biodata.rolename)) {
updates['system.biodata.rolename'] = role.name
}
if (Object.entries(updates).length > 0) {
await this.update(updates)
}
this.computeThreatLevel()
}
if (this.isOwner || game.user.isGM) {
// Update current hindrance level
let hindrance = this.system.combat.hindrancedice
if (!this.checkIgnoreHealth()) {
if (this.system.secondary.health.value < 0) {
if (this.system.secondary.health.value < -Math.floor((this.system.secondary.health.max + 1) / 2)) { // Severe wounded
hindrance += 3
} else {
hindrance += 1
}
}
}
this.system.combat.hindrancedice = hindrance
this.getTraumaState()
this.cleanupPerksIfTrauma()
}
}
/* -------------------------------------------- */
async modStat(key, inc = 1) {
let stat = duplicate(this.system.statistics[key])
stat.mod += parseInt(inc)
await this.update({ [`data.statistics.${key}`]: stat })
}
/* -------------------------------------------- */
async valueStat(key, inc = 1) {
key = key.toLowerCase()
let stat = duplicate(this.system.statistics[key])
stat.value += parseInt(inc)
await this.update({ [`data.statistics.${key}`]: stat })
}
/* -------------------------------------------- */
async addIncSpec(spec, inc = 1) {
console.log("Using spec : ", spec, inc)
let specExist = this.items.find(item => item.type == 'specialisation' && item.name.toLowerCase() == spec.name.toLowerCase())
if (specExist) {
specExist = duplicate(specExist)
specExist.system.level += inc;
let update = { _id: specExist._id, "data.level": specExist.system.level };
await this.updateEmbeddedDocuments('Item', [update]);
} else {
spec.system.level += inc;
await this.createEmbeddedDocuments('Item', [spec]);
}
}
/* -------------------------------------------- */
async incDecQuantity(objetId, incDec = 0) {
let objetQ = this.items.get(objetId)
if (objetQ) {
let newQ = objetQ.system.quantity + incDec
if (newQ >= 0) {
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantity': newQ }]) // pdates one EmbeddedEntity
}
}
}
/* -------------------------------------------- */
async incDecAmmo(objetId, incDec = 0) {
let objetQ = this.items.get(objetId)
if (objetQ) {
let newQ = objetQ.system.ammocurrent + incDec;
if (newQ >= 0 && newQ <= objetQ.system.ammomax) {
const updated = await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.ammocurrent': newQ }]); // pdates one EmbeddedEntity
}
}
}
/* -------------------------------------------- */
async applyAbility(ability, updates = [], directUpdate = false) {
// manage stat bonus
if (ability.system.affectedstat != "notapplicable") {
let stat = duplicate(this.system.statistics[ability.system.affectedstat])
stat.mod += Number(ability.system.statmodifier)
updates[`system.statistics.${ability.system.affectedstat}`] = stat
}
// manage status bonus
if (ability.system.statusaffected != "notapplicable") {
if (ability.system.statusaffected == 'nrg') {
let nrg = duplicate(this.system.nrg)
nrg.mod += Number(ability.system.statusmodifier)
updates[`system.nrg`] = nrg
}
if (ability.system.statusaffected == 'health') {
let health = duplicate(this.system.secondary.health)
health.bonus += Number(ability.system.statusmodifier)
updates[`system.secondary.health`] = health
}
if (ability.system.statusaffected == 'delirium') {
let delirium = duplicate(this.system.secondary.delirium)
delirium.bonus += Number(ability.system.statusmodifier)
updates[`system.secondary.delirium`] = delirium
}
}
if (directUpdate) {
await this.update(updates)
}
let newItems = []
if (ability.system.effectsgained) {
for (let effect of ability.system.effectsgained) {
newItems.push(effect);
}
}
if (ability.system.powersgained) {
for (let power of ability.system.powersgained) {
newItems.push(power);
}
}
if (ability.system.specialisations) {
for (let spec of ability.system.specialisations) {
newItems.push(spec);
}
}
if (ability.system.attackgained) {
for (let weapon of ability.system.attackgained) {
newItems.push(weapon);
}
}
if (ability.system.armorgained) {
for (let armor of ability.system.armorgained) {
newItems.push(armor);
}
}
console.log("Ability : adding", newItems)
await this.createEmbeddedDocuments('Item', newItems)
}
/* -------------------------------------------- */
async applyRace(race) {
let updates = { 'system.biodata.racename': race.name }
let newItems = []
await this.deleteAllItemsByType('race')
newItems.push(race);
console.log("DROPPED RACE", race)
for (let ability of race.system.abilities) {
newItems.push(ability)
this.applyAbility(ability, updates)
}
if (race.system.perksgained) {
for (let power of race.system.perks) {
newItems.push(power);
}
}
await this.update(updates)
await this.createEmbeddedDocuments('Item', newItems)
console.log("Updates", updates, newItems)
console.log("Updated actor", this)
}
/* -------------------------------------------- */
getIncreaseStatValue(updates, statKey) {
let stat = duplicate(this.system.statistics[statKey])
stat.value += 1;
updates[`data.statistics.${statKey}`] = stat
}
/* -------------------------------------------- */
async applyRole(role) {
console.log("ROLE", role)
let updates = { 'system.biodata.rolename': role.name }
let newItems = []
await this.deleteAllItemsByType('role')
newItems.push(role)
this.getIncreaseStatValue(updates, role.system.statincrease1)
this.getIncreaseStatValue(updates, role.system.statincrease2)
if (role.system.specialability.length > 0) {
console.log("Adding ability", role.system.specialability)
newItems = newItems.concat(duplicate(role.system.specialability)) // Add new ability
this.applyAbility(role.system.specialability[0], newItems)
}
await this.update(updates)
await this.createEmbeddedDocuments('Item', newItems)
}
/* -------------------------------------------- */
addHindrancesList(effectsList) {
if (this.type == "character") {
if (this.system.combat.stunlevel > 0) {
effectsList.push({ label: "Stun Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 2 })
}
if (this.system.combat.hindrancedice > 0) {
effectsList.push({ label: "Wounds Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: this.system.combat.hindrancedice })
}
let overCapacity = Math.floor(this.encCurrent / this.getEncumbranceCapacity())
if (overCapacity > 0) {
effectsList.push({ label: "Encumbrance Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: overCapacity })
}
if (this.system.biodata.morality <= 0) {
effectsList.push({ label: "Morality Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 3 })
}
let effects = this.items.filter(item => item.type == 'effect')
for (let effect of effects) {
effect = duplicate(effect)
if (effect.system.hindrance) {
effectsList.push({ label: effect.name, type: "effect", foreign: true, actorId: this.id, applied: false, effect: effect, value: effect.system.effectlevel })
}
}
}
if (this.type == "vehicle") {
if (this.system.stun.value > 0) {
effectsList.push({ label: "Stun Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 2 })
}
if (this.isVehicleCrawling()) {
effectsList.push({ label: "Crawling Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 3 })
}
if (this.isVehicleSlow()) {
effectsList.push({ label: "Slow Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 1 })
}
if (this.isVehicleAverage()) {
effectsList.push({ label: "Average Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 1 })
}
if (this.isVehicleFast()) {
effectsList.push({ label: "Fast Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 3 })
}
if (this.isVehicleExFast()) {
effectsList.push({ label: "Ext. Fast Hindrance", type: "hindrance", foreign: true, actorId: this.id, applied: false, value: 5 })
}
}
}
/* -------------------------------------------- */
/* ROLL SECTION
/* -------------------------------------------- */
pushEffect(rollData, effect) {
if (this.getTraumaState() == "none" && !this.checkNoBonusDice()) {
rollData.effectsList.push({ label: effect.name, type: "effect", applied: false, effect: effect, value: effect.system.effectlevel })
} else {
if (!effect.system.bonusdice) { // Do not push bonus dice effect when TraumaState is activated
rollData.effectsList.push({ label: effect.name, type: "effect", applied: false, effect: effect, value: effect.system.effectlevel })
}
}
}
/* -------------------------------------------- */
addEffects(rollData, isInit = false, isPower = false, isPowerDmg = false) {
let effects = this.items.filter(item => item.type == 'effect')
for (let effect of effects) {
effect = duplicate(effect)
if (!effect.system.hindrance
&& (effect.system.stataffected != "notapplicable" || effect.system.specaffected.length > 0)
&& effect.system.stataffected != "special"
&& effect.system.stataffected != "powerroll"
&& effect.system.stataffected != "powerdmgroll") {
if (effect.system.effectstatlevel) {
effect.system.effectlevel = this.system.statistics[effect.system.effectstat].value
}
this.pushEffect(rollData, effect)
}
if (isPower && effect.system.stataffected == "powerroll") {
this.pushEffect(rollData, effect)
}
if (isPowerDmg && effect.system.stataffected == "powerdmgroll") {
this.pushEffect(rollData, effect)
}
}
}
/* -------------------------------------------- */
addArmorsShields(rollData, statKey = "none", useShield = false) {
if (statKey == 'phy') {
let armors = this.getArmors()
for (let armor of armors) {
rollData.armorsList.push({ label: `Armor ${armor.name}`, type: "armor", applied: false, value: armor.system.resistance })
}
}
if (useShield) {
let shields = this.items.filter(item => item.type == "shield" && item.system.equipped)
for (let sh of shields) {
rollData.armorsList.push({ label: `Shield ${sh.name}`, type: "shield", applied: false, value: sh.system.level })
}
}
}
addWeapons(rollData, statKey) {
let weapons = this.getWeapons()
for (let weapon of weapons) {
if (weapon.system.equipped && weapon.system.statistic == statKey) {
rollData.weaponsList.push({ label: `Attack ${weapon.name}`, type: "attack", applied: false, weapon: weapon, value: 0, damageDice: PegasusUtility.getDiceFromLevel(0) })
}
if (weapon.system.equipped && weapon.system.canbethrown && statKey == "agi") {
rollData.weaponsList.push({ label: `Attack ${weapon.name}`, type: "attack", applied: false, weapon: weapon, value: 0, damageDice: PegasusUtility.getDiceFromLevel(0) })
}
if (weapon.system.equipped && weapon.system.enhanced && weapon.system.enhancedstat == statKey) {
rollData.weaponsList.push({ label: `Enhanced Attack ${weapon.name}`, type: "enhanced", applied: false, weapon: weapon, value: weapon.system.enhancedlevel, damageDice: PegasusUtility.getDiceFromLevel(weapon.system.enhancedlevel) })
}
if (weapon.system.equipped && weapon.system.damagestatistic == statKey) {
rollData.weaponsList.push({ label: `Damage ${weapon.name}`, type: "damage", applied: false, weapon: weapon, value: weapon.system.damage, damageDice: PegasusUtility.getDiceFromLevel(weapon.system.damage) })
}
}
}
addEquipments(rollData, statKey) {
let equipments = this.getEquipmentsOnly()
for (let equip of equipments) {
if (equip.system.equipped && equip.system.stataffected == statKey) {
rollData.equipmentsList.push({ label: `Item ${equip.name}`, type: "item", applied: false, equip: equip, value: equip.system.level })
}
}
}
addVehicleWeapons(rollData, vehicle) {
if (vehicle) {
let modules = vehicle.items.filter(vehicle => vehicle.type == "vehicleweaponmodule")
if (modules && modules.length > 0) {
for (let module of modules) {
rollData.vehicleWeapons.push({ label: `Weapon ${module.name}`, type: "item", applied: false, weapon: module, value: module.system.damagedicevalue })
}
}
}
}
/* -------------------------------------------- */
getCommonRollData(statKey = undefined, useShield = false, isInit = false, isPower = false, subKey = "", vehicle = undefined) {
let rollData = PegasusUtility.getBasicRollData(isInit)
rollData.alias = this.name
rollData.actorImg = this.img
rollData.actorId = this.id
rollData.img = this.img
rollData.traumaState = this.getTraumaState()
rollData.levelRemaining = this.getLevelRemaining()
rollData.activePerks = duplicate(this.getActivePerks())
rollData.diceList = PegasusUtility.getDiceList()
rollData.noBonusDice = this.checkNoBonusDice()
rollData.dicePool = []
if (subKey == "melee-dmg" || subKey == "ranged-dmg" || subKey == "power-dmg") {
rollData.isDamage = true
}
if (statKey) {
rollData.statKey = statKey
rollData.stat = this.getStat(statKey)
rollData.statDicesLevel = rollData.stat.value || rollData.stat.level
rollData.statMod = rollData.stat.mod
if (vehicle) {
rollData.vehicle = duplicate(vehicle)
if (subKey == "melee-dmg") {
if (vehicle.isVehicleFullStop()) {
ui.notifications.warn("MR not added to Melee Damage due to Full Stop.")
} else {
rollData.statVehicle = vehicle.system.statistics.mr
}
this.addVehicleWeapons(rollData, vehicle)
}
if (subKey == "ranged-atk") {
rollData.statVehicle = vehicle.system.statistics.fc
}
if (subKey == "ranged-dmg") {
this.addVehicleWeapons(rollData, vehicle)
}
if (subKey == "defense") {
if (vehicle.isVehicleFullStop()) {
ui.notifications.warn("MAN not added to Defense due to Full Stop.")
} else {
rollData.statVehicle = vehicle.system.statistics.man
}
}
//this.addVehiculeHindrances(rollData.effectsList, vehicle)
//this.addVehicleBonus(rollData, vehicle)
}
rollData.specList = this.getRelevantSpec(statKey)
rollData.selectedSpec = "0"
if (statKey.toLowerCase() == "mr") {
rollData.img = "systems/fvtt-pegasus-rpg/images/icons/MR.webp"
} else {
rollData.img = `systems/fvtt-pegasus-rpg/images/icons/${rollData.stat.abbrev}.webp`
}
rollData.dicePool = rollData.dicePool.concat(PegasusUtility.buildDicePool("stat", rollData.statDicesLevel, rollData.stat.mod))
if (rollData.statVehicle) {
rollData.dicePool = rollData.dicePool.concat(PegasusUtility.buildDicePool("statvehicle", rollData.statVehicle.currentlevel, 0))
}
}
this.addEffects(rollData, isInit, isPower, subKey == "power-dmg")
this.addArmorsShields(rollData, statKey, useShield)
this.addWeapons(rollData, statKey, useShield)
this.addEquipments(rollData, statKey)
console.log("ROLLDATA", rollData)
return rollData
}
/* -------------------------------------------- */
getLevelRemainingList() {
let options = []
for (let i = 0; i <= this.system.biodata.maxlevelremaining; i++) {
options.push(`<option value="${i}">${i}</option>`)
}
return options.join("\n")
}
/* -------------------------------------------- */
getMaxLevelRemainingList() {
let options = []
for (let i = 0; i <= 12; i++) {
options.push(`<option value="${i}">${i}</option>`)
}
return options.join("\n")
}
/* -------------------------------------------- */
async startRoll(rollData) {
this.syncRoll(rollData);
//console.log("ROLL DATA", rollData)
let rollDialog = await PegasusRollDialog.create(this, rollData)
console.log(rollDialog)
rollDialog.render(true);
}
/* -------------------------------------------- */
powerDmgRoll(itemId) {
let power = this.items.get(itemId)
if (power) {
power = duplicate(power)
this.rollPool(power.system.dmgstatistic, false, "power-dmg")
}
}
/* -------------------------------------------- */
rollPool(statKey, useShield = false, subKey = "none", vehicle = undefined) {
let stat = this.getStat(statKey)
if (stat) {
let rollData = this.getCommonRollData(statKey, useShield, false, false, subKey, vehicle)
rollData.mode = "stat"
rollData.subKey = subKey
let def = stat.label
if (subKey) {
def = __subkey2title[subKey]
}
rollData.title = `Roll : ${def} `
rollData.img = "icons/dice/d12black.svg"
this.startRoll(rollData)
} else {
ui.notifications.warn("Statistic not found !");
}
}
/* -------------------------------------------- */
rollUnarmedAttack() {
let stat = this.getStat('com')
if (stat) {
let rollData = this.getCommonRollData(statKey)
rollData.mode = "stat"
rollData.title = `Unarmed Attack`;
rollData.damages = this.getStat('str');
this.startRoll(rollData);
} else {
ui.notifications.warn("Statistic not found !");
}
}
/*-------------------------------------------- */
rollStat(statKey) {
let stat = this.getStat(statKey);
if (stat) {
let rollData = this.getCommonRollData(statKey)
rollData.mode = "stat"
rollData.title = `Stat ${stat.label}`;
this.startRoll(rollData)
} else {
ui.notifications.warn("Statistic not found !");
}
}
/* -------------------------------------------- */
async rollSpec(specId) {
let spec = this.getOneSpec(specId)
if (spec) {
let rollData = this.getCommonRollData(spec.system.statistic)
rollData.mode = "spec"
rollData.title = `Spec. : ${spec.name} `
rollData.specList = [spec]
rollData.selectedSpec = spec._id
rollData.specName = spec.name
rollData.img = spec.img
rollData.specDicesLevel = spec.system.level
PegasusUtility.updateSpecDicePool(rollData)
this.startRoll(rollData)
} else {
ui.notifications.warn("Specialisation not found !");
}
}
/* -------------------------------------------- */
async rollMR(isInit = false, combatId = 0, combatantId = 0) {
let mr = duplicate(this.system.mr)
if (mr) {
mr.dice = PegasusUtility.getDiceFromLevel(mr.value);
let rollData = this.getCommonRollData("mr", false, isInit)
rollData.mode = "MR"
rollData.img = "systems/fvtt-pegasus-rpg/images/icons/MR.webp"
rollData.isInit = isInit
rollData.combatId = combatId
rollData.combatantId = combatantId
console.log("MR ROLL", rollData)
if (isInit) {
rollData.title = "MR / Initiative"
}
this.startRoll(rollData);
} else {
ui.notifications.warn("MR not found !");
}
}
/* -------------------------------------------- */
async rollArmor(armorId) {
let armor = this.items.get(armorId)
if (armor) {
let rollData = this.getCommonRollData(armor.system.statistic)
armor = duplicate(armor);
this.checkAndPrepareEquipment(armor);
rollData.mode = "armor"
rollData.armor = armor
rollData.title = `Armor : ${armor.name}`
rollData.isResistance = true;
rollData.img = armor.img
rollData.damageDiceLevel = armor.system.resistance
this.startRoll(rollData);
} else {
ui.notifications.warn("Armor not found !", weaponId);
}
}
/* -------------------------------------------- */
async rollPower(powerId) {
let power = this.items.get(powerId)
if (power) {
power = duplicate(power)
let rollData = this.getCommonRollData(power.system.statistic, false, false, true)
rollData.mode = "power"
rollData.power = power
rollData.title = `Power : ${power.name}`
rollData.img = power.img
this.startRoll(rollData);
} else {
ui.notifications.warn("Power not found !", powerId);
}
}
/* -------------------------------------------- */
/* VEHICLE STUFF */
manageCurrentSpeed(speed) {
if (speed == "fullstop") {
this.update({ 'system.secondary.moverange': "nomovement" })
}
if (speed == "crawling") {
this.update({ 'system.secondary.moverange': "threatzone" })
}
if (speed == "slow") {
this.update({ 'system.secondary.moverange': "close" })
}
if (speed == "average") {
this.update({ 'system.secondary.moverange': "medium" })
}
if (speed == "fast") {
this.update({ 'system.secondary.moverange': "long" })
}
if (speed == "extfast") {
this.update({ 'system.secondary.moverange': "extreme" })
}
}
/* -------------------------------------------- */
modifyVehicleStun(incDec) {
let stun = this.system.stun.value + incDec
this.update({ 'stun.value': stun })
}
/* -------------------------------------------- */
addTopSpeedBonus(topspeed, bonus) {
let num = __speed2Num[topspeed] + Number(bonus)
num = Math.max(0, num)
num = Math.min(num, __num2speed.length - 1)
return __num2speed[num]
}
/* -------------------------------------------- */
manageVehicleSpeedBonus(speed, name, stat, level) {
if (this.system.statistics.ad.currentspeed == speed) {
if (!this.items.find(effect => effect.system.isspeed == speed)) {
let effect = duplicate(__bonusEffect)
effect.name = name
effect.system.stataffected = stat
effect.system.effectlevel = level
effect.system.isspeed = speed
this.createEmbeddedDocuments("Item", [effect])
}
} else {
let effect = this.items.find(effect => effect.system.isspeed == speed)
if (effect) {
this.deleteEmbeddedDocuments("Item", [effect.id])
}
}
}
/* -------------------------------------------- */
async computeVehicleStats() {
if (this.type == "vehicle") {
for (let statDef of __statBuild) {
let sum = 0
let list = []
for (let moduleType of statDef.modules) {
list = list.concat(this.items.filter(item => item.type == moduleType))
}
if (list && list.length > 0) {
sum = list.reduce((value, item2) => value + Number(item2.system[statDef.itemfield]), 0)
}
//console.log("Processing", statDef.field, this.system.statistics[statDef.field].level, list, sum)
if (statDef.subfield) {
if (sum != Number(this.system.statistics[statDef.field][statDef.subfield])) {
//console.log("Update", statDef.field, statDef.subfield, sum, this.system.statistics[statDef.field][statDef.subfield])
this.update({ [`system.statistics.${statDef.field}.${statDef.subfield}`]: sum })
}
} else {
if (sum != Number(this.system.statistics[statDef.field].level)) {
this.update({ [`system.statistics.${statDef.field}.level`]: sum, [`system.statistics.${statDef.field}.currentlevel`]: sum })
if (statDef.additionnal1) {
if (sum != Number(this.system.statistics[statDef.field][statDef.additionnal1])) {
this.update({ [`system.statistics.${statDef.field}.${statDef.additionnal1}`]: sum })
}
}
if (statDef.additionnal2) {
if (sum != Number(this.system.statistics[statDef.field][statDef.additionnal2])) {
this.update({ [`system.statistics.${statDef.field}.${statDef.additionnal2}`]: sum })
}
}
}
}
}
// Top speed management
let mobility = this.items.find(item => item.type == "mobilitymodule")
let arcs = duplicate(this.system.arcs)
if (mobility) {
let propulsion = this.items.find(item => item.type == "propulsionmodule")
let bonus = (propulsion) ? propulsion.system.topspeed : 0
arcs.frontarc.topspeed = this.addTopSpeedBonus(mobility.system.ts_f, bonus)
arcs.rightarc.topspeed = mobility.system.ts_s
arcs.leftarc.topspeed = mobility.system.ts_s
arcs.toparc.topspeed = mobility.system.ts_s
arcs.bottomarc.topspeed = mobility.system.ts_s
arcs.reararc.topspeed = mobility.system.ts_r
} else {
arcs.frontarc.topspeed = "fullstop"
arcs.rightarc.topspeed = "fullstop"
arcs.leftarc.topspeed = "fullstop"
arcs.toparc.topspeed = "fullstop"
arcs.bottomarc.topspeed = "fullstop"
arcs.reararc.topspeed = "fullstop"
}
for (let key in this.system.arcs) {
if (this.system.arcs[key].topspeed != arcs[key].topspeed) {
this.update({ 'system.arcs': arcs })
}
}
// VMS management
let hull = this.items.find(item => item.type == "vehiclehull")
let modules = duplicate(this.system.modules)
if (hull) {
modules.totalvms = Number(hull.system.vms)
} else {
modules.totalvms = 0
}
let spaceList = this.items.filter(item => item.type == "vehiclemodule") || []
spaceList = spaceList.concat(this.items.filter(item => item.type == "vehicleweaponmodule") || [])
let space = 0
if (spaceList && spaceList.length > 0) {
space = spaceList.reduce((value, item2) => value + Number(item2.system.space), 0)
}
modules.usedvms = space
if (modules.totalvms != this.system.modules.totalvms || modules.usedvms != this.system.modules.usedvms) {
this.update({ 'system.modules': modules })
}
if (modules.usedvms > modules.totalvms) {
ui.notifications.warn("Warning! No more space available in cargo !!")
}
}
// Speed effect management
this.manageVehicleSpeedBonus("crawling", "Crawling MAN Bonus", "man", 3)
this.manageVehicleSpeedBonus("slow", "Slow MAN Bonus", "man", 1)
this.manageVehicleSpeedBonus("average", "Avoid attack Bonus", "all", 1)
this.manageVehicleSpeedBonus("fast", "Avoid attack Bonus", "all", 3)
this.manageVehicleSpeedBonus("extfast", "Avoid attack Bonus", "all", 5)
}
/* -------------------------------------------- */
getTotalCost() {
let sumCost = 0
for (let item of this.items) {
if (__isVehicle[item.type]) {
if (item.system.cost) {
sumCost += Number(item.system.cost)
}
}
}
return sumCost
}
/* -------------------------------------------- */
async preprocessItemVehicle(event, item, onDrop = false) {
if (item.type != "effect" && !__isVehicle[item.type]) {
ui.notifications.warn("You can't drop Character items over a vehicle sheet.")
return
}
//console.log(">>>>> item", item.type, __isVehicleUnique[item.type])
if (__isVehicleUnique[item.type]) {
let toDelList = []
for (let toDel of this.items) {
if (toDel.type == item.type) {
toDelList.push(toDel.id)
}
}
//console.log("TODEL : ", toDelList)
if (toDelList.length > 0) {
await this.deleteEmbeddedDocuments('Item', toDelList)
}
}
// Check size
if (item.type == "vehiclemodule" || item.type == "vehicleweaponmodule") {
item.system.space = item.system.space || 0
if (this.system.modules.usedvms + Number(item.system.space) > this.system.modules.totalvms) {
ChatMessage.create({ content: `No more room available to host module ${item.name}. Module is not added to the vehicle.` })
return false
}
}
return true
}
/* -------------------------------------------- */
getCrewList() {
let crew = []
for (let actorDef of this.system.crew) {
let actor = game.actors.get(actorDef.id)
if (actor) {
crew.push({ name: actor.name, img: actor.img, id: actor.id })
}
}
return crew
}
/* -------------------------------------------- */
addCrew(actorId) {
let crewList = duplicate(this.system.crew.filter(actorDef => actorDef.id != actorId) || [])
crewList.push({ id: actorId })
this.update({ 'system.crew': crewList })
}
/* -------------------------------------------- */
delCrew(actorId) {
let crewList = duplicate(this.system.crew.filter(actorDef => actorDef.id != actorId) || [])
this.update({ 'system.crew': crewList })
}
/* -------------------------------------------- */
isVehicleFullStop() {
return this.system.statistics.ad.currentspeed == "fullstop"
}
isVehicleCrawling() {
return this.system.statistics.ad.currentspeed == "crawling"
}
isVehicleSlow() {
return this.system.statistics.ad.currentspeed == "slow"
}
isVehicleAverage() {
return this.system.statistics.ad.currentspeed == "average"
}
isVehicleFast() {
return this.system.statistics.ad.currentspeed == "fast"
}
isVehicleExFast() {
return this.system.statistics.ad.currentspeed == "extfast"
}
/* -------------------------------------------- */
isValidActor() {
// Find relevant actor
let actor
for (let actorDef of this.system.crew) {
let actorTest = game.actors.get(actorDef.id)
if (actorTest.testUserPermission(game.user, "OWNER")) {
return actorTest
}
}
if (!actor) {
ui.notifications.warn("You do no own any actors in the crew of this vehicle.")
return
}
}
/* -------------------------------------------- */
rollPoolFromVehicle(statKey, useShield = false, subKey = "none") {
let actor = this.isValidActor()
if (actor) {
actor.rollPool(statKey, useShield, subKey, this)
}
}
/* -------------------------------------------- */
addVehicleShields(rollData) {
let shields = this.items.filter( shield => shield.type == "vehiclemodule" && shield.system.activated && shield.system.shielddicevalue > 0) || []
for (let shield of shields) {
rollData.vehicleShieldList.push({ label: `${shield.name} (${shield.system.arccoverage})`, type: "vehicleshield", applied: false, value: shield.system.shielddicevalue })
}
}
/* -------------------------------------------- */
rollVehicleDamageResistance() {
let actor = this.isValidActor()
if (actor) {
let stat = this.getStat("hr")
let rollData = this.getCommonRollData("hr")
rollData.mode = "stat"
rollData.title = `Stat ${stat.label}`;
this.addVehicleShields(rollData)
this.startRoll(rollData)
}
}
/* -------------------------------------------- */
activateVehicleModule(itemId) {
let mod = this.items.get(itemId)
if (mod) {
this.updateEmbeddedDocuments('Item', [{ _id: mod.id, 'system.activated': !mod.system.activated }])
}
}
}