Skip to content

Commit

Permalink
Merge pull request #63 from orffen/50-add-additional-sheets
Browse files Browse the repository at this point in the history
50 add additional sheets
  • Loading branch information
orffen committed Sep 26, 2023
2 parents 52e71e8 + c44795c commit 6a0827c
Show file tree
Hide file tree
Showing 27 changed files with 1,233 additions and 210 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ The system does not yet automatically reset initiative at the end of each combat

### Roll Formulas

To add ability bonuses/penalties to roll formulas (damage or special ability), you can use `@str.bonus`. Replace `str` with `int`, `wis`, `dex`, `con` or `cha`. You can use the full ability with just `@str`, for example if you wanted to do something like `d20<=@str`. `@lvl` is available as short-hand for the character level.
To add ability bonuses/penalties to roll formulas (damage or special ability), you can use `@str.bonus`. Replace `str` with `int`, `wis`, `dex`, `con` or `cha`. You can use the full ability value with `@str.value`, for example if you wanted to do something like `d20<=@str.value`. `@lvl` is available as short-hand for the character level.

### Character Special Abilities

Special Abilities are a flexible item type with just a description and a roll formula. They can be used for thief abilities (formula: d100), open doors checks (d6), or even just as text (formula left blank). Clicking the icon in the list will either roll the formula if present, or output the description to the chat window.
Special Abilities are a flexible item type with just a description and a roll formula and an optional target number. They can be used for thief abilities (formula: d100), open doors checks (d6), or even just as text (formula left blank). Clicking the icon in the list will either roll the formula if present, or output the description to the chat window.

### Monster Special Abilities

The monster sheet has a "special abilities" field. This field should be 0, 1, or 2, depending on how many asterisks appear after the monster's hit dice value. XP values and attack bonus for monsters are automatically calculated.

## License

All software components are licensed under the MIT license - see *LICENSE.txt* for details.
All software components are licensed under the MIT license - see [LICENSE.txt](https://raw.githubusercontent.com/orffen/basicfantasyrpg/main/LICENSE.txt) for details.

Basic Fantasy Role-Playing Game content is distributed under the terms of the [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/).

Expand Down
74 changes: 66 additions & 8 deletions lang/en.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
{
"ACTOR.TypeCharacter": "Character",
"ACTOR.TypeMonster": "Monster",
"ACTOR.TypeSiegeEngine": "Siege Engine",
"ACTOR.TypeStronghold": "Stronghold",
"ACTOR.TypeVehicle": "Vehicle",

"ERROR.InvalidTargetNumber": "Couldn't parse target number for",

"ITEM.TypeItem": "Equipment",
"ITEM.TypeWeapon": "Weapon",
"ITEM.TypeArmor": "Armor",
"ITEM.TypeSpell": "Spell",
"ITEM.TypeFeature": "Special Ability",
"ITEM.TypeFloor": "Floor",
"ITEM.TypeWall": "Wall",

"TYPES.Actor.character": "Character",
"TYPES.Actor.monster": "Monster",
"TYPES.Actor.siegeEngine": "Siege Engine",
"TYPES.Actor.stronghold": "Stronghold",
"TYPES.Actor.vehicle": "Vehicle",

"TYPES.Item.item": "Equipment",
"TYPES.Item.weapon": "Weapon",
"TYPES.Item.armor": "Armor",
"TYPES.Item.spell": "Spell",
"TYPES.Item.feature": "Special Ability",
"TYPES.Item.floor": "Floor",
"TYPES.Item.wall": "Wall",

"BASICFANTASYRPG.CombatTab": "Combat",
"BASICFANTASYRPG.DescriptionTab": "Description",
"BASICFANTASYRPG.ItemsTab": "Equipment",
"BASICFANTASYRPG.SpellsTab": "Spells",
"BASICFANTASYRPG.FeaturesTab": "Special Abilities",
"BASICFANTASYRPG.TabCargo": "Cargo",
"BASICFANTASYRPG.TabCombat": "Combat",
"BASICFANTASYRPG.TabDescription": "Description",
"BASICFANTASYRPG.TabFeatures": "Special Abilities",
"BASICFANTASYRPG.TabFloors": "Floors & Walls",
"BASICFANTASYRPG.TabItems": "Equipment",
"BASICFANTASYRPG.TabSpells": "Spells",

"BASICFANTASYRPG.AbilityStr": "Strength",
"BASICFANTASYRPG.AbilityCon": "Constitution",
Expand All @@ -48,54 +62,98 @@
"BASICFANTASYRPG.Silver": "Silver",
"BASICFANTASYRPG.Copper": "Copper",

"BASICFANTASYRPG.AbilityCheck": "Ability Check",
"BASICFANTASYRPG.Add": "Add",
"BASICFANTASYRPG.Age": "Age",
"BASICFANTASYRPG.Area": "Area",
"BASICFANTASYRPG.ArmorClass": "Armor Class",
"BASICFANTASYRPG.ArmorClassAbbr": "AC",
"BASICFANTASYRPG.Attack": "Attack",
"BASICFANTASYRPG.AttackBonus": "Attack Bonus",
"BASICFANTASYRPG.AttackBonusAbbr": "AB",
"BASICFANTASYRPG.AttackPenalty": "Attack Penalty",
"BASICFANTASYRPG.BonusAttackBonus": "Bonus AB",
"BASICFANTASYRPG.BuildTime": "Time to Build",
"BASICFANTASYRPG.Cargo": "Cargo",
"BASICFANTASYRPG.CarriedWeight": "Load",
"BASICFANTASYRPG.Class": "Class",
"BASICFANTASYRPG.Cost": "Cost",
"BASICFANTASYRPG.CostMultiplier": "Cost Multiplier",
"BASICFANTASYRPG.Damage": "Damage",
"BASICFANTASYRPG.DamageAbbr": "Dmg",
"BASICFANTASYRPG.Days": "Days",
"BASICFANTASYRPG.Duration": "Duration",
"BASICFANTASYRPG.ExperiencePoints": "Experience Points",
"BASICFANTASYRPG.ExperiencePointsAbbr": "XP",
"BASICFANTASYRPG.Failure": "Failure",
"BASICFANTASYRPG.FeetAbbr": "ft.",
"BASICFANTASYRPG.Floors": "Floors",
"BASICFANTASYRPG.Followers": "Followers",
"BASICFANTASYRPG.Formula": "Roll Formula",
"BASICFANTASYRPG.Hardness": "Hardness",
"BASICFANTASYRPG.Height": "Height",
"BASICFANTASYRPG.HitDice": "Hit Dice",
"BASICFANTASYRPG.HitDiceAbbr": "HD",
"BASICFANTASYRPG.HitPoints": "Hit Points",
"BASICFANTASYRPG.HitPointsAbbr": "HP",
"BASICFANTASYRPG.Immobilized": "Immobilized",
"BASICFANTASYRPG.InitiativeBonus": "Init. Bonus",
"BASICFANTASYRPG.Length": "Length",
"BASICFANTASYRPG.Level": "Level",
"BASICFANTASYRPG.Maneuverability": "Maneuverability",
"BASICFANTASYRPG.Material": "Material",
"BASICFANTASYRPG.MaterialBrick": "Brick",
"BASICFANTASYRPG.MaterialStoneHard": "Hard Stone",
"BASICFANTASYRPG.MaterialStoneSoft": "Soft Stone",
"BASICFANTASYRPG.MaterialWood": "Wood",
"BASICFANTASYRPG.Melee": "Melee",
"BASICFANTASYRPG.Morale": "Morale",
"BASICFANTASYRPG.Movement": "Move",
"BASICFANTASYRPG.Movement": "Movement",
"BASICFANTASYRPG.Name": "Name",
"BASICFANTASYRPG.NextLevel": "Next Level",
"BASICFANTASYRPG.NumberAppearing": "No. Appearing",
"BASICFANTASYRPG.PoundsAbbr": "lbs.",
"BASICFANTASYRPG.Prepared": "Prepared",
"BASICFANTASYRPG.Price": "Price",
"BASICFANTASYRPG.Quantity": "Quantity",
"BASICFANTASYRPG.Race": "Race",
"BASICFANTASYRPG.Range": "Range",
"BASICFANTASYRPG.RangeBonus": "Range Bonus",
"BASICFANTASYRPG.Ranged": "Ranged",
"BASICFANTASYRPG.RangeLong": "Long (-2)",
"BASICFANTASYRPG.RangeMedium": "Medium (+0)",
"BASICFANTASYRPG.RangeShort": "Short (+1)",
"BASICFANTASYRPG.RateOfFire": "Rate of Fire",
"BASICFANTASYRPG.Roll": "Roll",
"BASICFANTASYRPG.RollUnder": "Roll Under",
"BASICFANTASYRPG.RoofSlate": "Slate-shingled Roof",
"BASICFANTASYRPG.RoofThatched": "Thatched Roof",
"BASICFANTASYRPG.RoofWood": "Wood-shingled Roof",
"BASICFANTASYRPG.SavingThrow": "Saving Throw",
"BASICFANTASYRPG.Sex": "Gender",
"BASICFANTASYRPG.SideAft": "Aft",
"BASICFANTASYRPG.SideForward": "Forward",
"BASICFANTASYRPG.SidePort": "Port",
"BASICFANTASYRPG.SideStarboard": "Starboard",
"BASICFANTASYRPG.Size": "Size",
"BASICFANTASYRPG.SpecialAbility": "Special Abilities",
"BASICFANTASYRPG.Spells": "Spells",
"BASICFANTASYRPG.SpecialAbility": "Special Ability",
"BASICFANTASYRPG.SpecialAbilityXPBonus": "Special Ability XP Bonus",
"BASICFANTASYRPG.SpellLevel": "Level",
"BASICFANTASYRPG.Spells": "Spells",
"BASICFANTASYRPG.SpellsPerLevel": "Spells",
"BASICFANTASYRPG.Success": "Success",
"BASICFANTASYRPG.Sunk": "Sunk",
"BASICFANTASYRPG.TargetAC": "Target AC",
"BASICFANTASYRPG.TargetNumber": "Target Number",
"BASICFANTASYRPG.Thick": "Thick",
"BASICFANTASYRPG.Thickness": "Thickness",
"BASICFANTASYRPG.TreasureType": "Treasure Type",
"BASICFANTASYRPG.Versus": "Versus",
"BASICFANTASYRPG.VersusAbbr": "vs.",
"BASICFANTASYRPG.Weight": "Weight",
"BASICFANTASYRPG.Width": "Width",
"BASICFANTASYRPG.WorkerDays": "Worker-days",
"BASICFANTASYRPG.Workers": "Workers",

"BASICFANTASYRPG.EffectCreate": "Create Effect",
"BASICFANTASYRPG.EffectToggle": "Toggle Effect",
Expand Down
120 changes: 81 additions & 39 deletions module/basicfantasyrpg.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Import document classes.
import { BasicFantasyRPGActor } from "./documents/actor.mjs";
import { BasicFantasyRPGItem } from "./documents/item.mjs";
import { BasicFantasyRPGActor } from './documents/actor.mjs';
import { BasicFantasyRPGItem } from './documents/item.mjs';
// Import sheet classes.
import { BasicFantasyRPGActorSheet } from "./sheets/actor-sheet.mjs";
import { BasicFantasyRPGItemSheet } from "./sheets/item-sheet.mjs";
import { BasicFantasyRPGActorSheet } from './sheets/actor-sheet.mjs';
import { BasicFantasyRPGItemSheet } from './sheets/item-sheet.mjs';
// Import helper/utility classes and constants.
import { preloadHandlebarsTemplates } from "./helpers/templates.mjs";
import { BASICFANTASYRPG } from "./helpers/config.mjs";
import { preloadHandlebarsTemplates } from './helpers/templates.mjs';
import { BASICFANTASYRPG } from './helpers/config.mjs';

/* -------------------------------------------- */
/* Init Hook */
Expand All @@ -30,7 +30,7 @@ Hooks.once('init', async function() {
* @type {String}
*/
CONFIG.Combat.initiative = {
formula: "max(1, 1d6 + @abilities.dex.bonus + @initBonus.value)",
formula: 'max(1, 1d6 + @abilities.dex.bonus + @initBonus.value)',
decimals: 0
};

Expand All @@ -39,80 +39,122 @@ Hooks.once('init', async function() {
CONFIG.Item.documentClass = BasicFantasyRPGItem;

// Register sheet application classes
Actors.unregisterSheet("core", ActorSheet);
Actors.registerSheet("basicfantasyrpg", BasicFantasyRPGActorSheet, { makeDefault: true });
Items.unregisterSheet("core", ItemSheet);
Items.registerSheet("basicfantasyrpg", BasicFantasyRPGItemSheet, { makeDefault: true });
Actors.unregisterSheet('core', ActorSheet);
Actors.registerSheet('basicfantasyrpg', BasicFantasyRPGActorSheet, { makeDefault: true });
Items.unregisterSheet('core', ItemSheet);
Items.registerSheet('basicfantasyrpg', BasicFantasyRPGItemSheet, { makeDefault: true });

// Preload Handlebars templates.
return preloadHandlebarsTemplates();
});

/* -------------------------------------------- */
/* Handlebars Helpers */
/* Handlebars Helpers & Partials */
/* -------------------------------------------- */

// If you need to add Handlebars helpers, here are a few useful examples:
Handlebars.registerHelper('concat', function() {
var outStr = '';
for (var arg in arguments) {
if (typeof arguments[arg] != 'object') {
outStr += arguments[arg];
}
Handlebars.registerHelper('calculateAbilityTargetNumber', function(lvl) {
return Math.floor(17 - (lvl / 2 - lvl % 2));
});

Handlebars.registerHelper('localizeFloorMaterial', function(type) {
switch (type) {
case 'roofThatch': return game.i18n.localize('BASICFANTASYRPG.RoofThatched');
case 'roofSlate' : return game.i18n.localize('BASICFANTASYRPG.RoofSlate');
case 'roofWood' : return game.i18n.localize('BASICFANTASYRPG.RoofWood');
default : return game.i18n.localize('ITEM.TypeFloor');
}
return outStr;
});

Handlebars.registerHelper('toLowerCase', function(str) {
return str.toLowerCase();
Handlebars.registerHelper('localizeWallMaterial', function(type) {
switch (type) {
case 'stoneHard' : return game.i18n.localize('BASICFANTASYRPG.MaterialStoneHard');
case 'stoneSoft' : return game.i18n.localize('BASICFANTASYRPG.MaterialStoneSoft');
case 'brick' : return game.i18n.localize('BASICFANTASYRPG.MaterialBrick');
case 'wood' : return game.i18n.localize('BASICFANTASYRPG.MaterialWood');
default : return game.i18n.localize('ITEM.TypeFloor');
}
});

Handlebars.registerHelper('localizeItemNameForActor', function(type) {
if (type === 'stronghold') {
return game.i18n.localize('ITEM.TypeFloor');
} else if (type === 'vehicle') {
return game.i18n.localize('BASICFANTASYRPG.Cargo');
} else {
return game.i18n.localize('ITEM.TypeItem');
}
});

Handlebars.registerHelper('localizeLowerCase', function(str) {
return game.i18n.localize(str).toLowerCase();
});

Handlebars.registerHelper('calculateAbilityTargetNumber', function(lvl) {
return Math.floor(17 - (lvl / 2 - lvl % 2));
})
Handlebars.registerHelper('toLowerCase', function(str) {
return str.toLowerCase();
});

Handlebars.registerHelper('selected', function(value) {
return Boolean(value) ? "selected" : "";
});

Handlebars.registerPartial('iconDamage', `<i class="fa-solid fa-heart-crack fa-2xl" title="{{localize 'BASICFANTASYRPG.Roll'}} {{localize 'BASICFANTASYRPG.Damage'}}"></i>`);
//`<img src="systems/basicfantasyrpg/styles/damage.svg" title="{{localize 'BASICFANTASYRPG.Roll'}} {{localize 'BASICFANTASYRPG.Damage'}}" width="24" height="24"/>`

Handlebars.registerPartial('iconMelee', `<i class="fa-solid fa-hand-fist fa-2xl" title="{{localize 'BASICFANTASYRPG.Roll'}} {{localize 'BASICFANTASYRPG.Melee'}} {{localize 'BASICFANTASYRPG.Attack'}}"></i>`);
//`<img src="systems/basicfantasyrpg/styles/melee.svg" title="{{localize 'BASICFANTASYRPG.Roll'}} {{localize 'BASICFANTASYRPG.Melee'}} {{localize 'BASICFANTASYRPG.Attack'}}" width="24" height="24"/>`

Handlebars.registerPartial('iconRanged', `<i class="fa-solid fa-crosshairs fa-2xl" title="{{localize 'BASICFANTASYRPG.Roll'}} {{localize 'BASICFANTASYRPG.Ranged'}} {{localize 'BASICFANTASYRPG.Attack'}}"></i>`);
//`<img src="systems/basicfantasyrpg/styles/ranged.svg" title="{{localize 'BASICFANTASYRPG.Roll'}} {{localize 'BASICFANTASYRPG.Ranged'}} {{localize 'BASICFANTASYRPG.Attack'}}" width="24" height="24"/>`

/* -------------------------------------------- */
/* Ready Hook */
/* Ready Hook & Others */
/* -------------------------------------------- */

Hooks.once("ready", async function() {
Hooks.once('ready', async function() {
// Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
Hooks.on("hotbarDrop", (bar, data, slot) => createItemMacro(data, slot));
Hooks.on('hotbarDrop', (bar, data, slot) => createItemMacro(data, slot));
});

// Hide certain types from being created through the UI
Hooks.on("renderDialog", (dialog, html) => {
let hiddenTypes = ["floor", "wall"];
Array.from(html.find("#document-create option")).forEach(i => {if (hiddenTypes.includes(i.value)) i.remove()});
});

/* -------------------------------------------- */
/* Character Creation Hooks */
/* -------------------------------------------- */

Hooks.on("createActor", async function(actor) {
if (actor.type === "character") {
Hooks.on('createActor', async function(actor) {
if (actor.type === 'character') {
actor.updateSource({
prototypeToken: {
actorLink: true,
disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
}
});
}
else if (actor.type === "monster") {
} else if (actor.type === 'monster') {
actor.updateSource({
prototypeToken: {
appendNumber: true,
displayName: CONST.TOKEN_DISPLAY_MODES.OWNER
}
});
} else if (actor.type === 'stronghold') {
const floor = {
name: `New ${game.i18n.localize('ITEM.TypeFloor')}`,
type: 'floor'
};
if (!actor.items.size) await actor.createEmbeddedDocuments('Item', [floor]);
}
});

/* -------------------------------------------- */
/* Token Creation Hooks */
/* -------------------------------------------- */

Hooks.on("createToken", async function(token, options, id) {
if (token.actor.type === "monster") {
Hooks.on('createToken', async function(token, options, id) {
if (token.actor.type === 'monster') {
let newHitPoints = new Roll(`${token.actor.system.hitDice.number}${token.actor.system.hitDice.size}+${token.actor.system.hitDice.mod}`);
await newHitPoints.evaluate({ async: true });
token.actor.system.hitPoints.value = Math.max(1, newHitPoints.total);
Expand All @@ -132,20 +174,20 @@ Hooks.on("createToken", async function(token, options, id) {
* @returns {Promise}
*/
async function createItemMacro(data, slot) {
if (data.type !== "Item") return;
if (!("data" in data)) return ui.notifications.warn("You can only create macro buttons for owned Items");
if (data.type !== 'Item') return;
if (!('data' in data)) return ui.notifications.warn('You can only create macro buttons for owned Items');
const item = data.data;

// Create the macro command
const command = `game.basicfantasyrpg.rollItemMacro("${item.name}");`;
const command = `game.basicfantasyrpg.rollItemMacro('${item.name}');`;
let macro = game.macros.find(m => (m.name === item.name) && (m.command === command));
if (!macro) {
macro = await Macro.create({
name: item.name,
type: "script",
type: 'script',
img: item.img,
command: command,
flags: { "basicfantasyrpg.itemMacro": true }
flags: { 'basicfantasyrpg.itemMacro': true }
});
}
game.user.assignHotbarMacro(macro, slot);
Expand Down

0 comments on commit 6a0827c

Please sign in to comment.