foundryvtt-shadows-over-sol/module/actor.js

388 lines
14 KiB
JavaScript
Raw Normal View History

2021-01-18 22:05:02 +01:00
import { SoSCardDeck } from "./sos-card-deck.js";
2021-01-17 22:09:01 +01:00
import { SoSUtility } from "./sos-utility.js";
2021-01-21 17:16:01 +01:00
import { SoSFlipDialog } from "./sos-flip-dialog.js";
2021-01-17 22:09:01 +01:00
/* -------------------------------------------- */
/**
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class SoSActor 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;
}
2021-01-31 17:23:14 +01:00
data.items = [];
let compendiumName = "foundryvtt-shadows-over-sol.skills";
if ( compendiumName ) {
let skills = await SoSUtility.loadCompendium(compendiumName);
data.items = data.items.concat( skills );
}
compendiumName = "foundryvtt-shadows-over-sol.consequences";
if ( compendiumName ) {
let consequences = await SoSUtility.loadCompendium(compendiumName)
data.items = data.items.concat(consequences);
}
2021-01-17 22:09:01 +01:00
return super.create(data, options);
}
/* -------------------------------------------- */
2021-01-18 22:05:02 +01:00
async prepareData() {
2021-01-17 22:09:01 +01:00
super.prepareData();
2021-01-18 22:05:02 +01:00
if ( !this.cardDeck ) {
2021-03-08 22:46:33 +01:00
if ( this.hasPlayerOwner) {
this.cardDeck = new SoSCardDeck();
this.cardDeck.initCardDeck( this, this.data.data.internals.deck );
} else {
this.cardDeck = game.system.sos.gmDeck;
}
2021-01-18 22:05:02 +01:00
}
this.controlScores();
}
2021-02-16 22:04:59 +01:00
/* -------------------------------------------- */
getDeckSize() {
return this.cardDeck.getDeckSize();
}
2021-01-18 22:05:02 +01:00
/* -------------------------------------------- */
getEdgesCard( ) {
let edgesCard = duplicate(this.cardDeck.data.cardEdge);
for (let edge of edgesCard) {
edge.path = `systems/foundryvtt-shadows-over-sol/img/cards/${edge.cardName}.webp`
}
return edgesCard;
}
/* -------------------------------------------- */
2021-01-31 17:39:37 +01:00
resetDeckFull( ) {
2021-01-19 22:54:53 +01:00
this.cardDeck.shuffleDeck();
2021-01-21 17:16:01 +01:00
this.cardDeck.drawEdge( this.data.data.scores.edge.value );
2021-01-19 22:54:53 +01:00
this.saveDeck();
}
2021-01-31 17:39:37 +01:00
/* -------------------------------------------- */
drawNewEdge( ) {
this.cardDeck.drawEdge( 1 );
this.saveDeck();
}
2021-02-11 00:07:13 +01:00
2021-02-16 23:01:42 +01:00
/* -------------------------------------------- */
discardEdge( cardName ) {
this.cardDeck.discardEdge( cardName );
this.saveDeck();
}
2021-01-31 17:39:37 +01:00
/* -------------------------------------------- */
2021-02-11 00:07:13 +01:00
resetDeck( ) {
this.cardDeck.resetDeck();
this.saveDeck();
2021-01-31 17:39:37 +01:00
}
2021-02-11 00:07:13 +01:00
2021-01-19 22:54:53 +01:00
/* -------------------------------------------- */
saveDeck( ) {
let deck = { deck: duplicate(this.cardDeck.data.deck),
2021-03-08 22:46:33 +01:00
discard: duplicate(this.cardDeck.data.discard),
cardEdge: duplicate(this.cardDeck.data.cardEdge)
}
if ( this.hasPlayerOwner ) {
this.update( { 'data.internals.deck': deck });
} else {
game.settings.set("foundryvtt-shadows-over-sol", "gmDeck", deck );
}
2021-01-17 22:09:01 +01:00
}
/* -------------------------------------------- */
2021-01-18 22:05:02 +01:00
getDefense( ) {
return this.data.data.scores.defense;
}
/* -------------------------------------------- */
computeDefense() {
return { value: Math.ceil((this.data.data.stats.speed.value + this.data.data.stats.perception.value + this.data.data.stats.dexterity.value) / 2),
critical: this.data.data.stats.speed.value + this.data.data.stats.perception.value + this.data.data.stats.dexterity.value
}
}
/* -------------------------------------------- */
getEdge( ) {
return this.data.data.scores.edge.value;
}
/* -------------------------------------------- */
getEncumbrance( ) {
return this.data.data.scores.encumbrance.value;
}
/* -------------------------------------------- */
computeEdge( ) {
2021-01-19 22:54:53 +01:00
return Math.ceil( (this.data.data.stats.intelligence.value + this.data.data.stats.charisma.value) / 2) + this.data.data.scores.edge.bonus;
2021-01-18 22:05:02 +01:00
}
/* -------------------------------------------- */
getShock( ) {
return this.data.data.scores.shock.value;
}
computeShock() {
return Math.ceil( this.data.data.stats.endurance.value + this.data.data.stats.determination.value + this.data.data.scores.dr.value);
}
/* -------------------------------------------- */
getWound( ) {
return this.data.data.scores.wound.value;
}
computeWound() {
return Math.ceil( (this.data.data.stats.strength.value + this.data.data.stats.endurance.value) / 2);
}
2021-02-09 23:32:55 +01:00
/* -------------------------------------------- */
async wornObject( itemID) {
let item = this.getOwnedItem(itemID);
if (item && item.data.data) {
let update = { _id: item._id, "data.worn": !item.data.data.worn };
await this.updateEmbeddedEntity("OwnedItem", update);
}
}
/* -------------------------------------------- */
async equipObject(itemID) {
let item = this.getOwnedItem(itemID);
if (item && item.data.data) {
let update = { _id: item._id, "data.equiped": !item.data.data.equiped };
await this.updateEmbeddedEntity("OwnedItem", update);
}
}
2021-01-18 22:05:02 +01:00
/* -------------------------------------------- */
async controlScores() {
// Defense check
let defenseData = this.getDefense();
let newDefenseData = this.computeDefense();
if ( defenseData.value != newDefenseData.value || defenseData.critical != newDefenseData.critical) {
await this.update( {'data.scores.defense': newDefenseData});
}
// Edge check
if ( this.getEdge() != this.computeEdge() ) {
await this.update( {'data.scores.edge.value': this.computeEdge()});
}
// Encumbrance
if ( this.getEncumbrance() != this.data.data.stats.strength.value ) {
await this.update( {'data.scores.encumbrance.value': this.data.data.stats.strength.value });
}
// Shock
if ( this.getShock() != this.computeShock() ) {
await this.update( {'data.scores.shock.value': this.computeShock() });
}
// Wounds
if ( this.getWound() != this.computeWound() ) {
await this.update( {'data.scores.wound.value': this.computeWound() });
}
}
2021-02-16 21:14:13 +01:00
/* -------------------------------------------- */
async updateWound(woundName, value) {
let wounds = duplicate(this.data.data.wounds)
wounds[woundName] = value;
await this.update( { 'data.wounds': wounds } );
}
2021-02-09 23:32:55 +01:00
/* -------------------------------------------- */
async updateSkill(skillName, value) {
let skill = this.data.items.find( item => item.name == skillName);
if (skill) {
const update = { _id: skill._id, 'data.value': value };
const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
async updateSkillExperience(skillName, value) {
let skill = this.data.items.find( item => item.name == skillName);
if (skill) {
const update = { _id: skill._id, 'data.xp': value };
const updated = await this.updateEmbeddedEntity("OwnedItem", update); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
getApplicableConsequences( ) {
let consequences = this.data.items.filter( item => item.type == 'consequence' && item.data.severity != 'none');
return consequences;
}
2021-01-18 22:05:02 +01:00
/* -------------------------------------------- */
2021-01-21 17:16:01 +01:00
async rollStat( statKey ) {
let flipData = {
mode: 'stat',
stat: duplicate(this.data.data.stats[statKey]),
actor: this,
2021-01-24 23:18:50 +01:00
modifierList: SoSUtility.fillRange(-10, +10),
2021-02-09 23:32:55 +01:00
tnList: SoSUtility.fillRange(6, 20),
consequencesList: duplicate( this.getApplicableConsequences() ),
2021-02-17 20:45:52 +01:00
wounds: duplicate( this.data.data.wounds),
2021-02-16 23:01:42 +01:00
malusConsequence: 0,
2021-02-17 22:38:12 +01:00
bonusConsequence: 0,
woundMalus: 0
2021-01-21 17:16:01 +01:00
}
let html = await renderTemplate('systems/foundryvtt-shadows-over-sol/templates/dialog-flip.html', flipData);
new SoSFlipDialog(flipData, html).render(true);
2021-01-17 22:09:01 +01:00
}
2021-01-24 23:18:50 +01:00
/* -------------------------------------------- */
async rollSkill( skill ) {
let flipData = {
mode: 'skill',
statList: duplicate(this.data.data.stats),
2021-02-11 00:07:13 +01:00
selectedStat: 'strength',
consequencesList: duplicate( this.getApplicableConsequences() ),
2021-02-17 20:45:52 +01:00
wounds: duplicate( this.data.data.wounds),
2021-02-11 00:07:13 +01:00
skill: duplicate(skill),
actor: this,
modifierList: SoSUtility.fillRange(-10, +10),
tnList: SoSUtility.fillRange(6, 20),
2021-02-16 23:01:42 +01:00
malusConsequence: 0,
2021-02-17 22:38:12 +01:00
bonusConsequence: 0,
woundMalus: 0
2021-02-11 00:07:13 +01:00
}
flipData.statList['nostat'] = { label: "No stat (ie defaulting skills)", value: 0, cardsuit: "none" }
let html = await renderTemplate('systems/foundryvtt-shadows-over-sol/templates/dialog-flip.html', flipData);
new SoSFlipDialog(flipData, html).render(true);
}
/* -------------------------------------------- */
async rollWeapon( weapon ) {
let target = SoSUtility.getTarget();
let skill, selectedStatName;
if ( weapon.data.data.category == 'ballistic' || weapon.data.data.category == 'laser' ) {
skill = this.data.items.find( item => item.name == 'Guns');
selectedStatName = 'dexterity';
} else if ( weapon.data.data.category == 'melee' ) {
skill = this.data.items.find( item => item.name == 'Melee');
selectedStatName = 'dexterity';
} else if ( weapon.data.data.category == 'grenade' ) {
skill = this.data.items.find( item => item.name == 'Athletics');
selectedStatName = 'dexterity';
}
let flipData = {
mode: 'weapon',
weapon: duplicate(weapon.data),
statList: duplicate(this.data.data.stats),
target: target,
selectedStat: selectedStatName,
2021-02-09 23:32:55 +01:00
consequencesList: duplicate( this.getApplicableConsequences() ),
2021-02-17 20:45:52 +01:00
wounds: duplicate( this.data.data.wounds),
2021-01-24 23:18:50 +01:00
skill: duplicate(skill),
actor: this,
modifierList: SoSUtility.fillRange(-10, +10),
2021-02-09 23:32:55 +01:00
tnList: SoSUtility.fillRange(6, 20),
2021-02-17 22:38:12 +01:00
malusConsequence: 0,
bonusConsequence: 0,
woundMalus: 0
2021-01-24 23:18:50 +01:00
}
2021-02-11 00:07:13 +01:00
console.log(flipData);
2021-01-25 11:16:45 +01:00
flipData.statList['nostat'] = { label: "No stat (ie defaulting skills)", value: 0, cardsuit: "none" }
2021-01-24 23:18:50 +01:00
let html = await renderTemplate('systems/foundryvtt-shadows-over-sol/templates/dialog-flip.html', flipData);
new SoSFlipDialog(flipData, html).render(true);
}
2021-02-15 23:21:53 +01:00
2021-02-16 21:14:13 +01:00
/* -------------------------------------------- */
async checkDeath( ) {
if ( this.data.data.scores.currentwounds.value >= this.data.data.scores.wound.value*2) {
let woundData = {
name: this.name,
wounds: this.data.data.wounds,
currentWounds: this.data.data.scores.currentwounds.value,
totalWounds: this.data.data.scores.wound.value
}
let html = await renderTemplate('systems/foundryvtt-shadows-over-sol/templates/chat-character-death.html', woundData );
ChatMessage.create( { content: html, whisper: [ChatMessage.getWhisperRecipients(this.name), ChatMessage.getWhisperRecipients("GM") ] } );
}
}
/* -------------------------------------------- */
computeCurrentWounds( ) {
let wounds = this.data.data.wounds;
return wounds.light + (wounds.moderate*2) + (wounds.severe*3) + (wounds.critical*4);
}
2021-02-15 23:21:53 +01:00
/* -------------------------------------------- */
async applyConsequenceWound( severity, consequenceName) {
if ( severity == 'none') return; // Nothing !
let wounds = duplicate(this.data.data.wounds);
if (severity == 'light' ) wounds.light += 1;
if (severity == 'moderate' ) wounds.moderate += 1;
if (severity == 'severe' ) wounds.severe += 1;
if (severity == 'critical' ) wounds.critical += 1;
let sumWound = wounds.light + (wounds.moderate*2) + (wounds.severe*3) + (wounds.critical*4);
let currentWounds = duplicate(this.data.data.scores.currentwounds);
currentWounds.value = sumWound;
await this.update( { 'data.scores.currentwounds': currentWounds, 'data.wounds': wounds } );
let woundData = {
name: this.name,
consequenceName: consequenceName,
severity: severity,
wounds: wounds,
currentWounds: sumWound,
totalWounds: this.data.data.scores.wound.value
}
let html = await renderTemplate('systems/foundryvtt-shadows-over-sol/templates/chat-damage-consequence.html', woundData );
2021-02-17 22:38:12 +01:00
ChatMessage.create( { content: html, whisper: ChatMessage.getWhisperRecipients(this.name).concat(ChatMessage.getWhisperRecipients("GM")) } );
2021-02-16 21:14:13 +01:00
this.checkDeath();
2021-02-15 23:21:53 +01:00
}
/* -------------------------------------------- */
async applyWounds( flipData ) {
let wounds = duplicate(this.data.data.wounds);
for (let wound of flipData.woundsList ) {
if (wound == 'L' ) wounds.light += 1;
if (wound == 'M' ) wounds.moderate += 1;
if (wound == 'S' ) wounds.severe += 1;
if (wound == 'C' ) wounds.critical += 1;
}
// Compute total
let sumWound = wounds.light + (wounds.moderate*2) + (wounds.severe*3) + (wounds.critical*4);
let currentWounds = duplicate(this.data.data.scores.currentwounds);
currentWounds.value = sumWound;
if ( sumWound >= this.data.data.scores.wound.value) {
let bleeding = this.data.items.find( item => item.type == 'consequence' && item.name == 'Bleeding');
let newSeverity = SoSUtility.increaseConsequenceSeverity( bleeding.severity );
await this.updateOwnedItem( { _id: bleeding._id, 'data.severity': newSeverity});
flipData.isBleeding = newSeverity;
}
await this.update( { 'data.scores.currentwounds': currentWounds, 'data.wounds': wounds } );
2021-02-16 21:14:13 +01:00
2021-02-15 23:21:53 +01:00
flipData.defenderName = this.name;
flipData.wounds = wounds;
flipData.currentWounds = sumWound;
flipData.totalWounds = this.data.data.scores.wound.value;
let html = await renderTemplate('systems/foundryvtt-shadows-over-sol/templates/chat-damage-taken.html', flipData );
ChatMessage.create( { content: html, whisper: [ChatMessage.getWhisperRecipients(this.name), ChatMessage.getWhisperRecipients("GM") ] } );
2021-02-16 21:14:13 +01:00
this.checkDeath();
2021-02-15 23:21:53 +01:00
}
2021-01-17 22:09:01 +01:00
}