Vastly improved new character UX

Characters now can limit which libraries they allow
This commit is contained in:
Stefan Zermatten
2022-07-18 13:45:14 +02:00
parent bf9639ae59
commit f8e9131bdd
21 changed files with 686 additions and 503 deletions

View File

@@ -131,15 +131,14 @@ function insertPropertyFromNode(nodeId, ancestors, order){
node.order = order;
}
// Mark root as dirty
node.dirty = true;
// Mark all nodes as dirty
dirtyNodes(nodes);
// Insert the creature properties
CreatureProperties.batchInsert(nodes);
return node;
}
function storeLibraryNodeReferences(nodes){
nodes.forEach(node => {
if (node.libraryNodeId) return;
@@ -147,6 +146,12 @@ function storeLibraryNodeReferences(nodes){
});
}
function dirtyNodes(nodes) {
nodes.forEach(node => {
node.dirty = true;
});
}
// Covert node references into actual nodes
// TODO: check permissions for each library a reference node references
function reifyNodeReferences(nodes, visitedRefs = new Set(), depth = 0){

View File

@@ -80,6 +80,27 @@ let CreatureSchema = new SimpleSchema({
optional: true,
max: STORAGE_LIMITS.url,
},
// Libraries
allowedLibraries: {
type: Array,
optional: true,
maxCount: 100,
},
'allowedLibraries.$': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
allowedLibraryCollections: {
type: Array,
optional: true,
maxCount: 100,
},
'allowedLibraryCollections.$': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
// Mechanics
deathSave: {
type: deathSaveSchema,
@@ -165,8 +186,8 @@ CreatureSchema.extend(SharingSchema);
Creatures.attachSchema(CreatureSchema);
import '/imports/api/creature/creatures/methods/index.js';
import '/imports/api/engine/actions/doAction.js';
export default Creatures;
export { CreatureSchema };
import '/imports/api/engine/actions/doAction.js';

View File

@@ -10,7 +10,7 @@ export default function defaultCharacterProperties(creatureId){
{
type: 'propertySlot',
name: 'Ruleset',
description: {text: 'Choose a starting point for your character, this will define the basic setup of your character sheet. Without a base, your sheet will be empty.'},
description: {text: 'Choose a starting point for your character, this will define the basic setup of your character sheet. Without a base ruleset, your sheet will be empty.'},
slotTags: ['base'],
tags: [],
quantityExpected: {calculation: '1'},

View File

@@ -1,57 +1,104 @@
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
import Creatures from '/imports/api/creature/creatures/Creatures.js';
import simpleSchemaMixin from '/imports/api/creature/mixins/simpleSchemaMixin.js';
import Creatures, { CreatureSchema } from '/imports/api/creature/creatures/Creatures.js';
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
import defaultCharacterProperties from '/imports/api/creature/creatures/defaultCharacterProperties.js';
import insertPropertyFromLibraryNode from '/imports/api/creature/creatureProperties/methods/insertPropertyFromLibraryNode.js';
import assertHasCharactersSlots from '/imports/api/creature/creatures/methods/assertHasCharacterSlots.js';
import getSlotFillFilter from '/imports/api/creature/creatureProperties/methods/getSlotFillFilter.js';
import getUserLibraryIds from '/imports/api/library/getUserLibraryIds.js';
import LibraryNodes from '/imports/api/library/LibraryNodes.js';
import { insertExperienceForCreature } from '/imports/api/creature/experience/Experiences.js';
import SimpleSchema from 'simpl-schema';
const insertCreature = new ValidatedMethod({
name: 'creatures.insertCreature',
validate: null,
mixins: [RateLimiterMixin],
mixins: [RateLimiterMixin, simpleSchemaMixin],
schema: CreatureSchema.pick(
'name',
'gender',
'alignment',
'allowedLibraries',
'allowedLibraryCollections',
).extend({
'startingLevel': {
type: SimpleSchema.Integer,
min: 0,
},
}),
rateLimit: {
numRequests: 5,
timeInterval: 5000,
},
run() {
if (!this.userId) {
run({ name, gender, alignment, startingLevel,
allowedLibraries, allowedLibraryCollections }) {
const userId = this.userId
if (!userId) {
throw new Meteor.Error('Creatures.methods.insert.denied',
'You need to be logged in to insert a creature');
'You need to be logged in to insert a creature');
}
assertHasCharactersSlots(this.userId);
assertHasCharactersSlots(userId);
// Create the creature document
// Create the creature document
let creatureId = Creatures.insert({
owner: this.userId,
});
owner: userId,
name,
gender,
alignment,
allowedLibraries,
allowedLibraryCollections,
});
// Insert experience to get character to starting level
if (startingLevel) {
insertExperienceForCreature({
experience: {
name: 'Starting level',
levels: startingLevel,
creatureId
},
creatureId,
userId,
});
}
// Insert the default properties
// Not batchInsert because we want the properties cleaned by the schema
let baseId;
let baseId, rulesetSlot;
defaultCharacterProperties(creatureId).forEach(prop => {
let id = CreatureProperties.insert(prop);
if (prop.name === 'Ruleset'){
baseId = id;
rulesetSlot = prop;
}
});
if (Meteor.isServer){
// Insert the 5e ruleset as the default base
insertPropertyFromLibraryNode.call({
nodeIds: ['iHbhfcg3AL5isSWbw'],
parentRef: {id: baseId, collection: 'creatureProperties'},
order: 0.5,
});
// If the user only has a single ruleset subscribed, use it by default
if (Meteor.isServer) {
insertDefaultRuleset(baseId, userId, rulesetSlot);
}
return creatureId;
},
});
// If the user only has a single ruleset subscribed, insert it by default
function insertDefaultRuleset(baseId, userId, slot) {
const libraryIds = getUserLibraryIds(userId);
const filter = getSlotFillFilter({ slot, libraryIds });
const fillCursor = LibraryNodes.find(filter, { fields: { _id: 1 } });
const numRulesets = fillCursor.count();
if (numRulesets === 1) {
const ruleset = fillCursor.fetch()[0]
insertPropertyFromLibraryNode.call({
nodeIds: [ruleset._id],
parentRef: {id: baseId, collection: 'creatureProperties'},
order: 0.5,
});
}
}
export default insertCreature;

View File

@@ -47,7 +47,6 @@ let ExperienceSchema = new SimpleSchema({
Experiences.attachSchema(ExperienceSchema);
const insertExperienceForCreature = function({experience, creatureId, userId}){
assertEditPermission(creatureId, userId);
if (experience.xp){
Creatures.update(creatureId, {
$inc: { 'denormalizedStats.xp': experience.xp },
@@ -93,6 +92,7 @@ const insertExperience = new ValidatedMethod({
}
let insertedIds = [];
creatureIds.forEach(creatureId => {
assertEditPermission(creatureId, userId);
let id = insertExperienceForCreature({experience, creatureId, userId});
insertedIds.push(id);
});
@@ -181,4 +181,4 @@ const recomputeExperiences = new ValidatedMethod({
});
export default Experiences;
export { ExperienceSchema, insertExperience, removeExperience, recomputeExperiences };
export { ExperienceSchema, insertExperience, insertExperienceForCreature, removeExperience, recomputeExperiences };