Migrated creature computations to use the new data structure for creature properties
This commit is contained in:
@@ -16,3 +16,4 @@ notices-for-facebook-graph-api-2
|
||||
1.4.3-split-account-service-packages
|
||||
1.5-add-dynamic-import-package
|
||||
1.7-split-underscore-from-meteor-base
|
||||
1.8.3-split-jquery-from-blaze
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
accounts-password@1.5.1
|
||||
accounts-password@1.5.2
|
||||
accounts-ui@1.3.1
|
||||
random@1.1.0
|
||||
dburles:collection-helpers
|
||||
@@ -13,26 +13,26 @@ matb33:collection-hooks
|
||||
momentjs:moment
|
||||
dburles:mongo-collection-instances
|
||||
percolate:migrations
|
||||
accounts-google@1.3.2
|
||||
accounts-google@1.3.3
|
||||
splendido:accounts-meld
|
||||
email@1.2.3
|
||||
meteorhacks:subs-manager
|
||||
chuangbo:marked
|
||||
meteor-base@1.4.0
|
||||
mobile-experience@1.0.5
|
||||
mongo@1.6.2
|
||||
mongo@1.8.0
|
||||
session@1.2.0
|
||||
jquery@1.11.10
|
||||
tracker@1.2.0
|
||||
logging@1.1.20
|
||||
reload@1.3.0
|
||||
ejson@1.1.0
|
||||
ejson@1.1.1
|
||||
check@1.3.1
|
||||
standard-minifier-js@2.4.1
|
||||
standard-minifier-js@2.6.0
|
||||
shell-server@0.4.0
|
||||
seba:minifiers-autoprefixer
|
||||
templates:array
|
||||
ecmascript@0.12.4
|
||||
ecmascript@0.14.0
|
||||
es5-shim@4.8.0
|
||||
reactive-dict@1.3.0
|
||||
percolate:synced-cron
|
||||
|
||||
@@ -1 +1 @@
|
||||
METEOR@1.8.1
|
||||
METEOR@1.9
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
accounts-base@1.4.4
|
||||
accounts-base@1.5.0
|
||||
accounts-google@1.3.3
|
||||
accounts-oauth@1.1.16
|
||||
accounts-password@1.5.1
|
||||
accounts-password@1.5.3
|
||||
accounts-ui@1.3.1
|
||||
accounts-ui-unstyled@1.4.2
|
||||
akryum:npm-check@0.1.2
|
||||
akryum:vue-component@0.15.0
|
||||
akryum:vue-component-dev-client@0.4.6
|
||||
akryum:vue-component@0.15.2
|
||||
akryum:vue-component-dev-client@0.4.7
|
||||
akryum:vue-component-dev-server@0.1.4
|
||||
akryum:vue-router2@0.2.3
|
||||
aldeed:collection2@3.0.2
|
||||
aldeed:collection2@3.0.6
|
||||
aldeed:schema-index@3.0.0
|
||||
allow-deny@1.1.0
|
||||
autoupdate@1.6.0
|
||||
babel-compiler@7.3.4
|
||||
babel-runtime@1.3.0
|
||||
babel-compiler@7.5.1
|
||||
babel-runtime@1.5.0
|
||||
base64@1.0.12
|
||||
binary-heap@1.0.11
|
||||
blaze@2.3.3
|
||||
blaze@2.3.4
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.6.0
|
||||
caching-compiler@1.2.1
|
||||
caching-html-compiler@1.1.3
|
||||
callback-hook@1.1.0
|
||||
callback-hook@1.3.0
|
||||
check@1.3.1
|
||||
chuangbo:marked@0.3.5_1
|
||||
dburles:collection-helpers@1.1.0
|
||||
@@ -35,11 +35,11 @@ ddp-server@2.3.0
|
||||
deps@1.0.12
|
||||
diff-sequence@1.1.1
|
||||
dynamic-import@0.5.1
|
||||
ecmascript@0.12.7
|
||||
ecmascript@0.14.1
|
||||
ecmascript-runtime@0.7.0
|
||||
ecmascript-runtime-client@0.8.0
|
||||
ecmascript-runtime-server@0.7.1
|
||||
ejson@1.1.0
|
||||
ecmascript-runtime-client@0.10.0
|
||||
ecmascript-runtime-server@0.9.0
|
||||
ejson@1.1.1
|
||||
email@1.2.3
|
||||
es5-shim@4.8.0
|
||||
fetch@0.1.1
|
||||
@@ -60,30 +60,30 @@ livedata@1.0.18
|
||||
lmieulet:meteor-coverage@1.1.4
|
||||
localstorage@1.2.0
|
||||
logging@1.1.20
|
||||
matb33:collection-hooks@0.8.4
|
||||
matb33:collection-hooks@0.9.1
|
||||
mdg:validated-method@1.2.0
|
||||
meteor@1.9.3
|
||||
meteor-base@1.4.0
|
||||
meteorhacks:picker@1.0.3
|
||||
meteorhacks:subs-manager@1.6.4
|
||||
meteortesting:browser-tests@1.0.0
|
||||
meteortesting:mocha@1.1.3
|
||||
meteortesting:mocha-core@1.0.1
|
||||
minifier-css@1.4.2
|
||||
minifier-js@2.4.1
|
||||
meteortesting:mocha@1.1.4
|
||||
meteortesting:mocha-core@7.0.1
|
||||
minifier-css@1.5.0
|
||||
minifier-js@2.6.0
|
||||
minimongo@1.4.5
|
||||
mobile-experience@1.0.5
|
||||
mobile-status-bar@1.0.14
|
||||
modern-browsers@0.1.4
|
||||
modules@0.13.0
|
||||
modules-runtime@0.10.3
|
||||
modern-browsers@0.1.5
|
||||
modules@0.15.0
|
||||
modules-runtime@0.12.0
|
||||
momentjs:moment@2.24.0
|
||||
mongo@1.6.3
|
||||
mongo@1.8.0
|
||||
mongo-decimal@0.1.1
|
||||
mongo-dev-server@1.1.0
|
||||
mongo-id@1.0.7
|
||||
npm-bcrypt@0.9.3
|
||||
npm-mongo@3.1.2
|
||||
npm-mongo@3.3.0
|
||||
oauth@1.2.8
|
||||
oauth2@1.2.1
|
||||
observe-sequence@1.0.16
|
||||
@@ -111,7 +111,7 @@ spacebars-compiler@1.1.3
|
||||
splendido:accounts-emails-field@1.2.0
|
||||
splendido:accounts-meld@1.3.1
|
||||
srp@1.0.12
|
||||
standard-minifier-js@2.4.1
|
||||
standard-minifier-js@2.6.0
|
||||
static-html@1.2.2
|
||||
templates:array@1.0.3
|
||||
templating@1.3.2
|
||||
@@ -122,5 +122,5 @@ tmeasday:check-npm-versions@0.3.2
|
||||
tracker@1.2.0
|
||||
underscore@1.0.10
|
||||
url@1.2.0
|
||||
webapp@1.7.4
|
||||
webapp@1.8.2
|
||||
webapp-hashing@1.0.9
|
||||
|
||||
@@ -5,11 +5,7 @@ import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
import { assertEditPermission } from '/imports/api/creature/creaturePermissions.js';
|
||||
import Creatures from "/imports/api/creature/Creatures.js";
|
||||
import Attributes from "/imports/api/properties/Attributes.js";
|
||||
import Skills from "/imports/api/properties/Skills.js";
|
||||
import Effects from "/imports/api/properties/Effects.js";
|
||||
import Proficiencies from "/imports/api/properties/Proficiencies.js";
|
||||
import DamageMultipliers from "/imports/api/properties/DamageMultipliers.js";
|
||||
import CreatureProperties from "/imports/api/creature/CreatureProperties.js";
|
||||
import * as math from 'mathjs';
|
||||
import parser from '/imports/parser/parser.js';
|
||||
if (Meteor.isClient) console.log({parser});
|
||||
@@ -89,7 +85,7 @@ export function recomputeCreatureById(charId){
|
||||
*/
|
||||
function writeCreature(char) {
|
||||
writeAttributes(char);
|
||||
writeSkills(char);
|
||||
writeCreatureProperties(char);
|
||||
writeDamageMultipliers(char);
|
||||
writeEffects(char);
|
||||
writeCreatureDoc(char);
|
||||
@@ -113,7 +109,7 @@ function writeAttributes(char) {
|
||||
let bulkWriteOps = _.map(char.atts, (att, variableName) => {
|
||||
let op = {
|
||||
updateMany: {
|
||||
filter: {charId: char.id, variableName},
|
||||
filter: {'ancestors.id': char.id, variableName},
|
||||
update: {$set: {
|
||||
value: att.result,
|
||||
}},
|
||||
@@ -127,12 +123,12 @@ function writeAttributes(char) {
|
||||
return op;
|
||||
});
|
||||
if (Meteor.isServer){
|
||||
Attributes.rawCollection().bulkWrite(bulkWriteOps, {ordered : false}, function(e, r){
|
||||
CreatureProperties.rawCollection().bulkWrite(bulkWriteOps, {ordered : false}, function(e, r){
|
||||
if (e) console.warn(JSON.stringify(e, null, 2));
|
||||
});
|
||||
} else {
|
||||
_.each(bulkWriteOps, op => {
|
||||
Attributes.update(op.updateMany.filter, op.updateMany.update, {multi: true});
|
||||
CreatureProperties.update(op.updateMany.filter, op.updateMany.update, {multi: true});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -147,27 +143,28 @@ function writeEffects(char){
|
||||
},
|
||||
}));
|
||||
if (Meteor.isServer){
|
||||
Effects.rawCollection().bulkWrite(bulkWriteOps, {ordered : false}, function(e, r){
|
||||
CreatureProperties.rawCollection().bulkWrite(bulkWriteOps, {ordered : false}, function(e, r){
|
||||
if (e) console.warn(JSON.stringify(e, null, 2));
|
||||
});
|
||||
} else {
|
||||
_.each(bulkWriteOps, op => {
|
||||
Effects.update(op.updateOne.filter, op.updateOne.update);
|
||||
CreatureProperties.update(op.updateOne.filter, op.updateOne.update);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write all the skills from the in-memory char object to the Skills docs
|
||||
* Write all the Creature Properties from the in-memory char object to the
|
||||
* properties docs
|
||||
*
|
||||
* @param {type} char description
|
||||
* @returns {type} description
|
||||
*/
|
||||
function writeSkills(char) {
|
||||
function writeCreatureProperties(char) {
|
||||
let bulkWriteOps = _.map(char.skills, (skill, variableName) => {
|
||||
let op = {
|
||||
updateMany: {
|
||||
filter: {charId: char.id, variableName},
|
||||
filter: {'ancestors.id': char.id, variableName},
|
||||
update: {$set: {
|
||||
value: skill.result,
|
||||
abilityMod: skill.abilityMod,
|
||||
@@ -182,12 +179,12 @@ function writeSkills(char) {
|
||||
return op;
|
||||
});
|
||||
if (Meteor.isServer){
|
||||
Skills.rawCollection().bulkWrite( bulkWriteOps, {ordered : false}, function(e, r){
|
||||
CreatureProperties.rawCollection().bulkWrite( bulkWriteOps, {ordered : false}, function(e, r){
|
||||
if (e) console.warn(JSON.stringify(e, null, 2));
|
||||
});
|
||||
} else {
|
||||
_.each(bulkWriteOps, op => {
|
||||
Skills.update(op.updateMany.filter, op.updateMany.update, {multi: true});
|
||||
CreatureProperties.update(op.updateMany.filter, op.updateMany.update, {multi: true});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -202,7 +199,7 @@ function writeDamageMultipliers(char) {
|
||||
let bulkWriteOps = _.map(char.dms, (dm, variableName) => {
|
||||
let op = {
|
||||
updateMany: {
|
||||
filter: {charId: char.id, variableName},
|
||||
filter: {'ancestors.id': char.id, variableName},
|
||||
update: {$set: {
|
||||
value: dm.result,
|
||||
}},
|
||||
@@ -211,12 +208,12 @@ function writeDamageMultipliers(char) {
|
||||
return op;
|
||||
});
|
||||
if (Meteor.isServer){
|
||||
DamageMultipliers.rawCollection().bulkWrite( bulkWriteOps, {ordered : false}, function(e, r){
|
||||
CreatureProperties.rawCollection().bulkWrite( bulkWriteOps, {ordered : false}, function(e, r){
|
||||
if (e) console.warn(JSON.stringify(e, null, 2));
|
||||
});
|
||||
} else {
|
||||
_.each(bulkWriteOps, op => {
|
||||
DamageMultipliers.update(op.updateMany.filter, op.updateMany.update, {multi: true});
|
||||
CreatureProperties.update(op.updateMany.filter, op.updateMany.update, {multi: true});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -224,7 +221,7 @@ function writeDamageMultipliers(char) {
|
||||
|
||||
/**
|
||||
* Get the creature's data from the database and build an in-memory model that
|
||||
* can be computed. Hits 7 database collections with indexed queries.
|
||||
* can be computed.
|
||||
*
|
||||
* @param {type} charId description
|
||||
* @returns {type} description
|
||||
@@ -241,162 +238,117 @@ function buildCreature(charId){
|
||||
computedEffects: [],
|
||||
level: 0,
|
||||
};
|
||||
// Fetch the attributes of the creature and add them to an object for quick lookup
|
||||
Attributes.find({charId}).forEach(attribute => {
|
||||
const key = attribute.variableName;
|
||||
if (!char.atts[key]){
|
||||
// Fetch the properties of the creature and add them to the char object for
|
||||
// quicker lookup
|
||||
CreatureProperties.find({'ancestors.id': charId}).forEach(prop => {
|
||||
const key = prop.variableName;
|
||||
// Attributes
|
||||
if (prop.type === 'attribute'){
|
||||
char.atts[key] = {
|
||||
computed: false,
|
||||
busyComputing: false,
|
||||
type: "attribute",
|
||||
attributeType: attribute.type,
|
||||
base: attribute.baseValue || 0,
|
||||
decimal: attribute.decimal,
|
||||
attributeType: prop.attributeType,
|
||||
base: prop.baseValue || 0,
|
||||
decimal: prop.decimal,
|
||||
result: 0,
|
||||
mod: 0, // The resulting modifier if this is an ability
|
||||
add: 0,
|
||||
mul: 1,
|
||||
min: Number.NEGATIVE_INFINITY,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
effects: [],
|
||||
};
|
||||
char.variables[key] = char.atts[key];
|
||||
if (attribute.type === 'ability' && !char.variables[key + "Mod"]){
|
||||
char.variables[key + "Mod"] = {
|
||||
type: "abilityMod",
|
||||
ability: char.atts[key],
|
||||
get result(){
|
||||
return this.ability.mod;
|
||||
},
|
||||
get computed(){
|
||||
return this.ability.computed;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch the skills of the creature and store them
|
||||
Skills.find({charId}).forEach(skill => {
|
||||
const key = skill.variableName;
|
||||
if (!char.skills[key]){
|
||||
char.skills[key] = {
|
||||
computed: false,
|
||||
busyComputing: false,
|
||||
type: "skill",
|
||||
ability: skill.ability,
|
||||
base: skill.baseValue,
|
||||
result: 0, // For skills the result is the skillMod
|
||||
proficiency: skill.baseProficiency || 0,
|
||||
add: 0,
|
||||
mul: 1,
|
||||
min: Number.NEGATIVE_INFINITY,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
advantage: 0,
|
||||
disadvantage: 0,
|
||||
passiveAdd: 0,
|
||||
fail: 0,
|
||||
conditional: 0,
|
||||
effects: [],
|
||||
proficiencies: [],
|
||||
};
|
||||
if (!char.variables[key]){
|
||||
char.variables[key] = char.skills[key];
|
||||
char.variables[key] = char.atts[key];
|
||||
}
|
||||
//Skill
|
||||
else if (prop.type === 'skill'){
|
||||
if (!char.skills[key]){
|
||||
char.skills[key] = {
|
||||
computed: false,
|
||||
busyComputing: false,
|
||||
type: "skill",
|
||||
ability: prop.ability,
|
||||
base: prop.baseValue,
|
||||
result: 0, // For skills the result is the skillMod
|
||||
proficiency: prop.baseProficiency || 0,
|
||||
add: 0,
|
||||
mul: 1,
|
||||
min: Number.NEGATIVE_INFINITY,
|
||||
max: Number.POSITIVE_INFINITY,
|
||||
advantage: 0,
|
||||
disadvantage: 0,
|
||||
passiveAdd: 0,
|
||||
fail: 0,
|
||||
conditional: 0,
|
||||
effects: [],
|
||||
proficiencies: [],
|
||||
};
|
||||
if (!char.variables[key]){
|
||||
char.variables[key] = char.skills[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch the damage multipliers of the creature and store them
|
||||
DamageMultipliers.find({charId}).forEach(damageMultiplier =>{
|
||||
const key = damageMultiplier.variableName;
|
||||
if (!char.dms[key]){
|
||||
char.dms[key] = {
|
||||
// Damage multipliers
|
||||
else if (prop.type === 'damageMultiplier'){
|
||||
if (!char.dms[key]){
|
||||
char.dms[key] = {
|
||||
computed: false,
|
||||
busyComputing: false,
|
||||
type: "damageMultiplier",
|
||||
result: 0,
|
||||
immunityCount: 0,
|
||||
ressistanceCount: 0,
|
||||
vulnerabilityCount: 0,
|
||||
effects: [],
|
||||
};
|
||||
if (!char.variables[key]){
|
||||
char.variables[key] = char.dms[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Classes
|
||||
//TODO
|
||||
// Effects
|
||||
else if (prop.type === 'effect'){
|
||||
let storedEffect = {
|
||||
_id: effect._id,
|
||||
computed: false,
|
||||
busyComputing: false,
|
||||
type: "damageMultiplier",
|
||||
result: 0,
|
||||
immunityCount: 0,
|
||||
ressistanceCount: 0,
|
||||
vulnerabilityCount: 0,
|
||||
effects: [],
|
||||
operation: prop.operation,
|
||||
calculation: prop.calculation,
|
||||
};
|
||||
if (!char.variables[key]){
|
||||
char.variables[key] = char.dms[key];
|
||||
if (char.atts[effect.stat]) {
|
||||
char.atts[effect.stat].effects.push(storedEffect);
|
||||
} else if (char.skills[effect.stat]) {
|
||||
char.skills[effect.stat].effects.push(storedEffect);
|
||||
} else if (char.dms[effect.stat]) {
|
||||
char.dms[effect.stat].effects.push(storedEffect);
|
||||
} else {
|
||||
char.otherEffects.push(storedEffect);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch the class levels and store them
|
||||
// don't use the word "class" it's reserved
|
||||
const levelOverwritten = !!char.variables.level;
|
||||
if (!levelOverwritten){
|
||||
char.variables.level = {
|
||||
result: 0,
|
||||
type: 'characterLevel',
|
||||
computed: true,
|
||||
};
|
||||
}
|
||||
Classes.find({charId}).forEach(cls => {
|
||||
const strippedCls = cls.name.replace(/\s+/g, '');
|
||||
if (!char.classes[strippedCls]){
|
||||
char.classes[strippedCls] = {level: cls.level};
|
||||
char.level += cls.level;
|
||||
if (!char.variables[strippedCls]){
|
||||
char.variables[strippedCls + "Level"] = {
|
||||
result: cls.level,
|
||||
type: 'classLevel',
|
||||
// Proficiencies
|
||||
else if (prop.type === 'proficiency'){
|
||||
if (char.skills[prop.skill]) {
|
||||
char.skills[prop.skill].proficiencies.push(proficiency);
|
||||
}
|
||||
}
|
||||
// Add direct properties from creature to variable list
|
||||
const fields = { xp: 1, weightCarried: 1};
|
||||
const creature = Creatures.findOne(charId, {fields});
|
||||
for (let key in fields){
|
||||
if (!char.variables[key]){
|
||||
char.variables[key] = {
|
||||
result: creature[key] || 0,
|
||||
type: 'creatureProperty',
|
||||
computed: true,
|
||||
};
|
||||
}
|
||||
if (!levelOverwritten){
|
||||
char.variables.level.result = char.level;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add direct properties from creature to variable list
|
||||
const fields = { xp: 1, weightCarried: 1};
|
||||
const creature = Creatures.findOne(charId, {fields});
|
||||
for (let key in fields){
|
||||
if (!char.variables[key]){
|
||||
char.variables[key] = {
|
||||
result: creature[key] || 0,
|
||||
type: 'creatureProperty',
|
||||
computed: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the effects which apply to each stat and store them under the attribute
|
||||
Effects.find({
|
||||
charId: charId,
|
||||
enabled: true,
|
||||
}).forEach(effect => {
|
||||
let storedEffect = {
|
||||
_id: effect._id,
|
||||
computed: false,
|
||||
result: 0,
|
||||
operation: effect.operation,
|
||||
calculation: effect.calculation,
|
||||
};
|
||||
if (char.atts[effect.stat]) {
|
||||
char.atts[effect.stat].effects.push(storedEffect);
|
||||
} else if (char.skills[effect.stat]) {
|
||||
char.skills[effect.stat].effects.push(storedEffect);
|
||||
} else if (char.dms[effect.stat]) {
|
||||
char.dms[effect.stat].effects.push(storedEffect);
|
||||
} else {
|
||||
char.otherEffects.push(storedEffect);
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch the proficiencies and store them under each skill
|
||||
Proficiencies.find({
|
||||
charId: charId,
|
||||
enabled: true,
|
||||
}).forEach(proficiency => {
|
||||
if (char.skills[proficiency.skill]) {
|
||||
char.skills[proficiency.skill].proficiencies.push(proficiency);
|
||||
}
|
||||
});
|
||||
return char;
|
||||
@@ -437,10 +389,6 @@ export function computeCreature(char){
|
||||
* @returns {type} description
|
||||
*/
|
||||
function computeStat(stat, char){
|
||||
// Ability mods aren't stats, use the stat they are based off of
|
||||
if (stat.type === 'abilityMod'){
|
||||
stat = stat.ability;
|
||||
}
|
||||
|
||||
// If the stat is already computed, skip it
|
||||
if (stat.computed) return;
|
||||
@@ -455,7 +403,6 @@ function computeStat(stat, char){
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Iterate over each effect which applies to the stat
|
||||
for (let i in stat.effects){
|
||||
computeEffect(stat.effects[i], char);
|
||||
@@ -540,7 +487,6 @@ function applyEffect(effect, stat){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combine the results of multiple effects to get the result of the stat
|
||||
*/
|
||||
@@ -554,7 +500,6 @@ function combineStat(stat, char){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* combineAttribute - Combine attributes's results into final values
|
||||
*/
|
||||
@@ -568,7 +513,6 @@ function combineAttribute(stat, char){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Combine skills results into final values
|
||||
*/
|
||||
@@ -685,7 +629,6 @@ export const recomputeCreatureXP = new ValidatedMethod({
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Recompute a character's weight carried from a given id
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user