const preact = require('preact'); const { useEffect } = require('preact/hooks'); const get = require('lodash/get'); const anime = require('animejs').default; const shapes = require('./components/shapes'); const stringSort = (k, desc) => { if (desc) { return (a, b) => { if (!get(a, k)) return 1; if (!get(b, k)) return -1; return get(b, k).localeCompare(get(a, k)); }; } return (a, b) => { if (!get(a, k)) return 1; if (!get(b, k)) return -1; return get(a, k).localeCompare(get(b, k)); }; }; const numSort = (k, desc) => { if (desc) { return (a, b) => { if (!get(a, k)) return 1; if (!get(b, k)) return -1; return get(b, k) - get(a, k); }; } return (a, b) => { if (!get(a, k)) return 1; if (!get(b, k)) return -1; return get(a, k) - get(b, k); }; }; const genAvatar = name => { let hash = 0; if (name.length === 0) return hash; // Probs don't need to hash using the whole string for (let i = 0; i < name.length; i += 1) { const chr = name.charCodeAt(i); hash = ((hash << 5) - hash) + chr; hash = hash % 10000; } return `${hash}`; }; function requestAvatar(name) { const id = genAvatar(name); const req = new Request(`/assets/molecules/${id}.svg`); return fetch(req) .then(res => res.text()) .then(svg => svg); } const animations = {}; function animateCryp(id) { if (animations[id]) return false; animations[id] = true; const target = document.getElementById(id); return anime({ targets: target, translateX: () => anime.random(-20, 20), translateY: () => anime.random(0, -40), rotate: () => anime.random(-15, 15), duration: () => anime.random(2000, 5000), delay: () => anime.random(0, 2000), direction: 'alternate', easing: 'linear', loop: true, complete: () => animations[id] = false, }); } function clearAnimation(id) { animations[id] = false; } function crypAvatar(name, id) { useEffect(() => { animateCryp(id); return () => clearAnimation(id); }); return ( event.target.setAttribute('src', '/molecules/726.svg')} /> ); } function instanceCryp(name, id) { useEffect(() => animateCryp(id)); return (
); } const NULL_UUID = '00000000-0000-0000-0000-000000000000'; const STATS = { redDamage: { stat: 'red_damage', colour: 'red', svg: shapes.circle, }, greenDamage: { stat: 'green_damage', colour: 'green', svg: shapes.circle, }, blueDamage: { stat: 'blue_damage', colour: 'blue', svg: shapes.circle, }, speed: { stat: 'speed', colour: 'white', svg: shapes.triangle, }, redLife: { stat: 'red_life', colour: 'red', svg: shapes.square, }, greenLife: { stat: 'green_life', colour: 'green', svg: shapes.square, }, blueLife: { stat: 'blue_life', colour: 'blue', svg: shapes.square, }, }; const SPECS = { Life: { colour: 'white', caption: 'Life', svg: shapes.square }, GreenLifeI: { colour: 'green', caption: 'Life', thresholds: [5, 10, 20], svg: shapes.square, }, RedLifeI: { colour: 'red', caption: 'Life', thresholds: [2, 5, 10], svg: shapes.square, }, BlueLifeI: { colour: 'blue', caption: 'Life', thresholds: [2, 5, 10], svg: shapes.square, }, GRLI: { colour: 'yellow', caption: 'Life', thresholds: [2, 5, 10], svg: shapes.square }, GBLI: { colour: 'cyan', caption: 'Life', thresholds: [2, 5, 10], svg: shapes.square }, RBLI: { colour: 'purple', caption: 'Life', thresholds: [2, 5, 10], svg: shapes.square }, Damage: { colour: 'white', caption: 'Damage', thresholds: [], svg: shapes.circle }, RedDamageI: { colour: 'red', caption: 'DamageI', thresholds: [5, 10, 20], svg: shapes.circle }, BlueDamageI: { colour: 'blue', caption: 'DamageI', thresholds: [5, 10, 20], svg: shapes.circle }, GreenDamageI: { colour: 'green', caption: 'DamageI', thresholds: [5, 10, 20], svg: shapes.circle, }, GRDI: { colour: 'yellow', caption: 'DamageI', thresholds: [2, 5, 10], svg: shapes.circle }, GBDI: { colour: 'cyan', caption: 'DamageI', thresholds: [2, 5, 10], svg: shapes.circle, }, RBDI: { colour: 'purple', caption: 'DamageI', thresholds: [2, 5, 10], svg: shapes.circle, }, Speed: { colour: 'white', caption: 'Speed', svg: shapes.triangle, }, RedSpeedI: { colour: 'red', caption: 'Speed', thresholds: [5, 10, 20], svg: shapes.triangle, }, BlueSpeedI: { colour: 'blue', caption: 'Speed', thresholds: [2, 5, 10], svg: shapes.triangle, }, GreenSpeedI: { colour: 'green', caption: 'Speed', thresholds: [2, 5, 10], svg: shapes.triangle, }, GRSpeedI: { colour: 'yellow', caption: 'Speed', thresholds: [2, 5, 10], svg: shapes.triangle, }, GBSpeedI: { colour: 'cyan', caption: 'Speed', thresholds: [2, 5, 10], svg: shapes.triangle, }, RBSpeedI: { colour: 'purple', caption: 'Speed', thresholds: [2, 5, 10], svg: shapes.triangle, }, }; const COLOUR_ICONS = { red: { colour: 'red', caption: 'red', svg: shapes.square }, blue: { colour: 'blue', caption: 'blue', svg: shapes.square }, green: { colour: 'green', caption: 'green', svg: shapes.square }, }; function resoCrypHealth(resolution, currentGame) { if (!resolution) return false; const modifyHealth = cryp => { if (cryp.id !== resolution.target.id) return false; // not target const [type, event] = resolution.event; if (type === 'Damage') { const { amount, mitigation, colour } = event; cryp.green_life.value -= amount; if (colour === 'Red') { cryp.red_life.value -= mitigation; } if (colour === 'Blue') { cryp.blue_life.value -= mitigation; } } if (type === 'Healing') { const { amount } = event; cryp.green_life.value += amount; } if (type === 'Recharge') { const { red, blue } = event; cryp.red_life.value += red; cryp.blue_life.value += blue; } return true; }; currentGame.players.forEach(player => player.cryps.forEach(modifyHealth)); return true; } function eventClasses(resolution, cryp) { if (!resolution) return ''; const startSkill = resolution.stage === 'START_SKILL'; const endSkill = resolution.stage === 'END_SKILL'; const postSkill = resolution.stage === 'POST_SKILL'; const source = cryp.id === resolution.source.id; const target = cryp.id === resolution.target.id; // not involved at all. blur them if (!(source || target)) return 'unfocus'; // not the target. just ignore for now // if (cryp.id !== resolution.target.id) return ''; const [type, event] = resolution.event; if (type === 'Ko') { if (target) return 'ko'; } if (type === 'Disable') { const { skill, disable } = event; } if (type === 'Immunity') { const { skill, immunity } = event; } if (type === 'TargetKo') { const { skill } = event; } if (type === 'Damage') { const { skill, amount, mitigation, colour } = event; // Highlight the flow of damage from source -> target if (source && startSkill) return 'active-skill'; if (target && endSkill) return 'active-skill'; // Deal damage to cryp and return effect if (target && postSkill) { if (colour === 'Red') { return 'red-damage'; } if (colour === 'Blue') { return 'blue-damage'; } if (colour === 'Green') return 'green-damage'; } } if (type === 'Healing') { const { skill, amount, overhealing } = event; if (source && startSkill) return 'active-skill'; if (target && endSkill) return 'active-skill'; if (target && postSkill) { return 'green-damage'; } } if (type === 'Inversion') { const { skill } = event; } if (type === 'Reflection') { const { skill } = event; } if (type === 'Effect') { const { skill, effect, duration } = event; if (source && startSkill) return 'active-skill'; if (target && endSkill) return 'active-skill'; } if (type === 'Skill') { const { skill } = event; // Highlight the flow of damage from source -> target if (source && startSkill) return 'active-skill'; if (target && endSkill) return 'active-skill'; } if (type === 'Removal') { const { effect } = event; } if (type === 'Recharge') { const { skill, red, blue } = event; if (source && startSkill) return 'active-skill'; if (target && endSkill) return 'active-skill'; if (target && postSkill) { if (red > 0 && blue > 0) return 'purple-damage'; if (red > 0) return 'red-damage'; if (blue > 0) return 'blue-damage'; } } if (type === 'Evasion') { const { skill, evasion_rating } = event; } return ''; } function getCombatSequence(event) { if (!event) return false; // Skip combat animations depending on event type, expandable in future const dotTicks = ['DecayTick', 'CorruptionTick', 'TriageTick', 'SiphonTick', 'StrangleTick']; if (['Skill', 'AoeSkill'].includes(event[0])) return ['START_SKILL', 'END_SKILL']; if (['Immunity'].includes(event[0])) return ['START_SKILL', 'POST_SKILL']; if (['Removal'].includes(event[0])) return ['POST_SKILL']; if (['Healing'].includes(event[0]) && (event[1].skill === 'Slay' || event[1].skill === 'SiphonTick' || event[1].skill === 'Purify' || event[1].skill === 'Sleep')) return ['POST_SKILL']; if (['Recharge'].includes(event[0]) && (event[1].skill === 'Reflect')) return ['POST_SKILL']; if (event[0] === 'Effect' && (['Ruin', 'Taunt', 'Strangling', 'Parry'].includes(event[1].skill) || (event[1].skill === 'Decay' && event[1].effect === 'Wither'))) return ['POST_SKILL']; if (['Damage'].includes(event[0]) && ((event[1].skill === 'Chaos' && event[1].colour === 'Red') || event[1].skill === 'Silence' || event[1].skill === 'Snare')) return ['POST_SKILL']; if (['Ko'].includes(event[0]) || (event[1].skill === 'Throw' && event[1].effect === 'Vulnerable')) return ['POST_SKILL']; if (dotTicks.includes(event[1].skill)) return ['END_SKILL', 'POST_SKILL']; return ['START_SKILL', 'END_SKILL', 'POST_SKILL']; } function getCombatText(cryp, resolution) { if (!resolution) return ['', '']; const [type, event] = resolution.event; const source = cryp.id === resolution.source.id; const target = cryp.id === resolution.target.id; const startSkill = resolution.stage === 'START_SKILL'; const endSkill = resolution.stage === 'END_SKILL'; const postSkill = resolution.stage === 'POST_SKILL'; if (type === 'Ko') { if (postSkill && target) return ['KO!', '']; } if (type === 'Disable') { const { skill, disable } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; if (postSkill && target) return [`${disable}`, '']; } if (type === 'Immunity') { const { skill, immunity } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; if (postSkill && target) return ['IMMUNE', '']; } if (type === 'TargetKo') { const { skill } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; } if (type === 'Damage') { const { skill, amount, mitigation, colour } = event; const mitigationText = mitigation ? `(${mitigation})` : ''; if (startSkill && source) return [`${skill}`, `${skill.toLowerCase()}-cast`]; if (endSkill && target) return [`${skill}`, `${skill.toLowerCase()}-hit`]; if (postSkill && target) return [`${amount} ${mitigationText}`, '']; } if (type === 'Healing') { const { skill, amount, overhealing } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; if (postSkill && target) return [`${amount} (${overhealing} OH)`, '']; } if (type === 'Inversion') { const { skill } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; if (postSkill && target) return ['INVERT', '']; } if (type === 'Reflection') { const { skill } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; if (postSkill && target) return ['REFLECT', '']; } if (type === 'Effect') { const { skill, effect, duration } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; if (postSkill && target) return [`+ ${effect} ${duration}T`, '']; } if (type === 'Recharge') { const { skill, red, blue } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; if (postSkill && target) return [`+${red}R ${blue}B`, '']; } if (type === 'Skill' || type === 'AoeSkill') { const { skill } = event; if (startSkill && source) return [`${skill}`, '']; if (endSkill && target) return [`${skill}`, '']; } if (type === 'Removal') { const { effect } = event; if (postSkill && target) return [`-${effect}`, '']; } return ''; } function convertItem(v) { if (['Red', 'Green', 'Blue'].includes(v)) { return ( shapes.vboxColour(v.toLowerCase()) ); } return v ||  ; // uncomment for double borders in vbox; // if (v) { // return
{v}
; // } // return; } module.exports = { animateCryp, stringSort, convertItem, numSort, genAvatar, crypAvatar, instanceCryp, requestAvatar, eventClasses, getCombatSequence, getCombatText, resoCrypHealth, NULL_UUID, STATS, SPECS, COLOUR_ICONS, };