Merge branch 'release/1.11.0'

This commit is contained in:
ntr 2020-01-03 18:38:03 +10:00
commit 9cf81735f1
129 changed files with 13894 additions and 8807 deletions

View File

@ -1,3 +1,15 @@
## [1.10.1] - 2019-12-04
### Changed
- Reduced the number of items to create + and ++ versions from 3 to 2 (need 4 items total to make a ++)
- Reduced the power of ++ skills to be closer to the power of previous + versions
- Tweaked all values of power specs to reduce their power bonuses
## [1.10.0] - 2019-11-29
### Changed
- Reworked the vbox layout
- Floating combat text and faster animations
- Mobile UI fixes
## [1.9.1] - 2019-11-21
### Fixed
- Fixed Item+ Purchasing Bug

266
COMBOS.md
View File

@ -1,142 +1,200 @@
# item_info ->
# Spec / Skill hybrid specs #
combos [strike, [R R Attack]]
specs [spec [bonus amount, [r g b]]
Create skills specs by combining an upgraded skills with corresponding colour specs:
Strike (RR + A) can be combined with (PowerRR, SpeedRR, LifeRR)
- Strike + PowerRR -> StrikePower
- Strike + SpeedRR -> StrikeSpeed
- Strike + LifeRR -> StrikeLife
# Playthrough
Could also create SkillSpec+ by combining two together.
constructs join game
stats randomised
## Why Skill Specs
initial stash drops
6 skills
6 colours
6 specs
- Give tools to players to make cool unique builds
- Passive utility that you work towards
- Specialise in a type of skill
- Repurpose skills that you aren't using much for active use
- More layers of complexity
play first round
basically duke it out
## Skill specs philosphy
# Colours #
- Should be more interesting than just another stat "multiplier" we have that already (could be placeholder though)
- Passives should help build a theme / identity to the skills
- Specs should be numerically scalable so they can be `balanced` (also for upgraded versions think continuous vs discrete)
- While specialised should be useful for multiple skills
e.g. StrikeSpeed -> Causes your red attack skills to cast on another target on the same team for (X% damage)
A StrikeSpeed should do something for `some` other active skill that isn't strike
- They don't have to be `competely game changing` to be viable / fun / interesting
### Red ###
Real world concepts
Aggressive
Apply Buffs
Fast & Chaotic
## Brainstorming on what kind of things skill specs could do
### Green ###
Healing Specialisation
Defensive
Purge buffs & debuffs
- Passive
- (Abosrb Spec) when you take damage you permanently gain X Power/Speed/Life
- Grant you Amplify with (X% multiplier) when you KO a target
- Convert (X% of your red / blue power -> green power)
- Reduce / Increase damage taken of a certain type (Block type upgrades maybe?)
### Blue ###
Fantasy concepts (magical)
Aggressive & Defensive
Apply Debuffs
Slow & Reliable
- Active
- Make a skill cast another skill (strike another guy)
- Apply an effect when you cast a skill
- Change damage / healing / speed of a skill
# Classes #
## The BIG LIST
Class names to be changed
====================
Pure Red `Nature`
Pure Green `Non-Violence`
Pure Blue `Destruction`
Hybrid Red / Blue `Chaos`
Hybrid Red / Green `Purity`
Hybrid Blue / Green `Technology`
### Attack Base
Strike
Power
Life
Speed (Repeat skill at X% multiplier)
Chaos
Power
Life
Speed
Skills
==========
Heal
Power (Convert X% Red/Blue Power to GreenPower)
Life
Speed
Basic Type
-------------------------------------------------------------------------
Attack `Basic offensive skill - deal damage`
Buff `Base ally targetted skill - increase ally speed`
Stun `Base enemy disable - disable enemy for 2 rounds`
Block `Base self targetted defensive - reduced damage taken for 2 rounds`
Debuff `Base enemy debuff - reduce enemy speed`
Blast
Power
Life
Speed
# Attack Base #
Slay
Power
Life
Speed
RR - Strike
GG - Heal
BB - Blast
RG - Purify
GB - Decay
RB - Blast
Siphon
Power
Life
Speed
# Stun Base #
### Stun Base
Bash
Power
Life
Speed
RR - Strangle
GG - Break
BB - Ruin
RG - Banish
GB - Silence
RB - Hex
Sleep
Power
Life
Speed
# Buff Base #
Ruin
Power
Life
Speed
RR - Empower
GR - Triage
BB - Absorb
RG - Sustain
GB - Amplify
RB - Haste
Link
Power
Life
Speed
# Debuff Base #
Banish
Power
Life
Speed
RR - Restrict
GG - Purge
BB - Curse
RG - Slow
GB - Siphon
RB - Invert
Break
Power
Life
Speed
# Block Base #
### Block Base
RR - Counter
GG - Reflect
BB - Electrify
RG - Intercept
GB - Life `rename?`
RB - Recharge
Counter
Power
Life
Speed
Reflect
Power
Life
Speed
## Advanced combos ##
Purify
Power
Life
Speed
Two ways of upgrading
#1 -> combine more of the same for a stronger version of the same skill / spec (T2 / T3 Combos)
#2 -> combine skill with two matching colour specs to change the way the skill works (Spec / Skill hybrid)
Sustain
Power
Life
Speed
### T2 / T3 Combos ###
Electrify
Power
Life
Speed
All current specs / items can be further combo'd into T2 and T3 versions
Recharge
Power
Life
Speed
# 3 of same base => 1 upgraded tier #
`3 x T1 Red Damage Spec => T2 Red Damage Spec`
`3 x T2 Red Damage Spec => T3 Red Damage Spec`
`3 x T1 Strike => T2 Strike`
`3 x T2 Strike => T3 Strike`
### Buff Base
Upgraded skills will have a combination of higher damage / longer duration / reduced cooldown
Upgraded skills use the same speed formula as previously
Intercept
Power
Life
Speed
### Spec / Skill hybrid specs ###
Triage
Power
Life
Speed
# Strike #
2 x Red Damage + Strike => Strike damage bonus (crit?)
2 x Red Speed + Strike => Strike reduces enemy speed
2 x Red Life + Strike => Strike reduces enemy healing (% reduction)
Absorb
Power (Gain X Power when you take damage)
Life (Gain X Life when you take damage)
Speed (Gain X Speed when you take damage)
# Heal #
2 x Green Damage + Heal => Heal target for additional 20% of caster's maximum life
2 x Green Speed + Heal => Heal target gets bonus speed
2 x Green Life + Heal => Heal increases target's max hp for 2 turns
Amplify
Power
Life
Speed
etc etc
Haste
Power
Life
Speed
30 skills * 3 specs => 90 spec / skill hybrid specs -> might be overcomplicated
Hybrid
Power
Life
Speed
### Debuff Base
Purge
Power
Life
Speed
Invert
Power
Life
Speed
Restrict
Power
Life
Speed
Silence
Power
Life
Speed
Curse
Power
Life
Speed
Decay
Power
Life
Speed

View File

@ -1,75 +0,0 @@
# Everything costs money (gold?)
Items - Base colours / skills / specs and associated upgrades
### Sources of money
- Start with money and gain income after each battle
- Higher income from winning
- Selling items in inventory or equipped on character refunds
- Selling from inventory full refund
- Selling from charcter 50% refund
### Uses for money
- Buying items
- Rerolling vbox
### Base Costs
Base colours have a base 1 cost
Base skills have a base 2 cost
Base specs have a base 3 cost
### Actual Costs
- Costs increase as more of an item is used on constructs in the game
- The cost increases by the base cost for every 6 allocations of base item
- Allocation is based on all constructs in the game
### Example ###
Round #1
All costs are base costs
# Player #1 and Player #2 (They both bought the same things)
Construct #1 Strike (Attack + RR), (2 + 1 + 1) = (4) cost
Construct #1 Empower (Buff + RR), (2 + 1 + 1) = (4) cost
Construct #3 Attack, 2 cost
Total cost - 10
Round #2
Items used on constructs include:
Red x 8
Attack x 4
Buff x 2
The costs of red for round #2 are now (1 + 1) = 2
If they were to buy the same skill setup it would be as follows:
# Player #1 and Player #2 (They both bought the same things)
Construct #1 Strike (Attack + RR), (2 + 2 + 2) = (6) cost
Construct #1 Empower (Buff + RR), (2 + 2 + 2) = (6) cost
Construct #3 Attack, 2 cost
Total cost - 14
### Philosophy of increasing item costs
- Two games will never feel exactly the same
- Costs change over rounds to diversify skill choice and gameplay
- As optimal builds emerge the paths to reach them will change every game
- Rewarded for going (hipster) builds nobody else is trying
- Some reward for hoarding items in your inventory while they cheaper (hodl red)
### Income values
Could try with 9 base income
Income increases by 3 each round and winning bonus of 6

View File

@ -1,49 +0,0 @@
# Stat Multipliers #
### Defenses ###
Rare `Increased GreenLife`
Common `Increased Evasion rating`
Common `Increased Blue Life rating`
Common `Increased RedLife rating`
Common `Increased Healing done`
Common `Increased Healing received`
Common `Increased Blue Damage`
Common `Increased Red Damage`
Uncommon `Reduced hp loss penalty to evade chance`
Uncommon `Increased base evasion chance per X evasion rating`
Uncommon `Increased % mitigation from red_life`
Uncommon `Increased % mitigation from spell shield`
Uncommon `Increased damage over time`
Rare `gain empower on KO`
Rare `cannot be restrictd`
Rare `cannot be silenced`
Rare `cannot be intercepted`
Rare `25% stun for attack`
Rare `25% hex for blast`
Rare `cooldown reduction`
Rare `effect duration`
Rare `increased phys damage, 0 spell damage`
Rare `increased spell damage, 0 phys damage`
Rare `increased phys damage, silenced`
Rare `increased spell damage, restrictd`
Rare `increased speed, increased durations`
Rare `increased speed, increased cooldowns`
# Nature - Technology - Nonviolence - Destruction - Purity - Chaos #
- Increased power
- Increased speed
- Increased stat
- ??? Related Notables
# ??? Constructs need to have a minimum of X of the construct stat to learn a skill #

View File

@ -34,8 +34,12 @@ Player Events e.g. chatwheel
Matchmaking + ELO / Leaderboard
Game skill private fields
# Phase 4 (Release -> Full Shill mode)
Refine artwork, icons, scaling etc
Music
Skill Specs
Some sort viewable combat log
Marketing materials
Videos

224
SPECS.md
View File

@ -1,224 +0,0 @@
### Specs ###
Numbers are placeholder
`Specs get a bonus dependent on the total of Red / Green / Blue in player skills & specs`
# Example to meet 5 red gem bonus from skills only
In your player Construct #1 has `Strike`, Construct #2 has `Slay` and `Heal`, Construct #3 has `Restrict`
- RR skill `Strike` contributes 2 red gems to the total red gems (2 total)
- RG skill `Slay` contributes 1 red gem to the total red gems (3 total)
- GG skill `Heal` contirubtes 0 red gems to the total red gems (3 total)
- RR skill `Restrict` contirubtes 2 red gems to the total red gems (5 total)
# Advanced specs also require a minimum number of Red / Green / Blue gems on the construct to take effect
- Tier 1 Basic specs (Damage / Health / Defense) will have no requirements
- Advanced specs will require a certain threshold of red / green / blue gems to be enabled
- Provided spec requirements are met, all specs will add gems to the construct
# Starting from scratch with a vbox
### Round 1
- Buy 4 reds (items)
- Buy two 'Attack' Skills & 1 Stun skill (items)
- Buy 1 Basic Damage Spec (item)
Combine 2 Red + 'Attack' -> Strike
Combine 2 Red + 'Basic Damage Spec' -> Red Damage
Construct #1 -> Give Strike & Red Damage Spec -> Strike + 1 x Red Damage Spec
Construct #2 -> Give Attack -> Attack
Construct #3 -> Give Stun -> Stun
Player Total (4 Red + 2 Basic gems)
### Round 2
- Buy 2 reds & 2 green & 2 blue (all available colour items)
- Buy 2 Basic Damage Spec (item)
- Construct #2 Unequip Attack
- Combine 2 Green + 'Attack' -> Heal
- Construct #3 Unequip Stun
- Combine 2 Blue + 'Stun' -> Ruin
- Combine 2 Red + 'Basic Damage Spec' -> Red Damage
Construct #1 -> Give Red Damage items -> Strike + 2 x Red Damage Spec (6R)
Construct #2 -> Give Heal item -> Heal (2G)
Construct #3 -> Give Ruin item -> Ruin (2B)
## Round 3
- Buy 4 reds
- Buy 1 Attack, 1 Stun, 1 Block (item)
- Buy 2 Basic Damage Spec (item)
- Combine 2 Red + 'Stun' -> Strangle
- Combine 2 Red + 'Block' -> Counter
Construct #1 -> Give 'Stun' & 'Strangle' -> Strike, Stun, Strangle + 2 x Red Damage Spec (10R)
Construct #2 -> 'No change' -> Heal (2G)
Construct #3 -> Give Attack item & 2 Basic Damage Spec -> Attack + Ruin + 2 x Basic Damage Spec (2B)
## Round 4
- Buy 4 reds (getting lucky with reds!)
- Buy 1 Attack, 1 Buff
- Combine 2 Red + 'Attack' -> Strike
- Combine 2 Red + 'Buff' -> Empower
- Construct #1 Unequip 2 x Red Damage spec, Equip Empower -> Strike, Stun, Strangle, Empower (8R)
- Combine 'Strike' + 2 x Red Damage spec -> 'Increased Strike Damage spec'
### Note 'Increased Strike Damage spec' requires 8R on the construct
Construct #1 Equip Increased Strike Damage spec -> Strike, Stun, Strangle, Empower + Increased Strike Damage Spec (14R)
Construct #2 -> 'No change' -> Heal
Construct #3 -> 'No change' -> Attack + Ruin + 2 x Basic Damage Spec
## Round 5
We already lost cause we went all in on 1 red construct like a noob
### Generic Specs
# Basic % GreenLife
`Base` -> 5% inc hp
`Player Bonus` -> 3 basic gems -> +5% // 6 basic gems -> +10% // 12 basic gems -> +15%
Maximum 35% inc hp
# Basic Speed
`Base` -> 5% inc speed
`Player Bonus` -> 3 basic gems -> +10% // 6 basic gems -> +15% // 12 basic gems -> +20%
Maximum 50% inc speed
# Basic Class Spec
`Base` -> +2 red, +2 green +2 blue gems on construct
# Basic Duration
### Increased Damage Combos ###
Generate by combining `Generic Spec (Basic Damage)` with respective RGB
# Red Damage (Dmg + RR)
Add 2 `red gems`
`Base` -> 10% inc red dmg
`Player Bonus` 5 red gems -> +10% // 10 red gems -> +15% // 20 red gems -> +25%
Maximum +60% red damage
# Blue Damage (Dmg + BB) #
Add 2 `blue gems`
`Base` -> 10% inc blue dmg
`Player Bonus` 5 blue gems -> +10% // 10 blue gems -> +15% // 20 blue gems -> +25%
Maximum +60% blue damage
# Healing (Dmg + GG) #
Add 2 `green gems`
`Base` -> 10% inc healing
`Player Bonus` 5 green gems -> +10% // 10 green gems -> +15% // 20 green gems -> +25%
Maximum +60% inc healing
# Red damage and healing (Dmg + RG)
Add 1 red 1 green gem
`Base` -> 5% inc red damage and 5% inc healing
`Player Bonus` (2R + 2G gems) -> +5% + 5% // (5R + 5G gems) -> +10% + 10% % // (10R + 10G) gems -> +15% + 15%
Maximum +35% inc red damage and 35% inc healing
# Red and blue damage (Dmg + RB)
Add 1 red and 1 blue gem
`Base` -> 5% inc red damage and 5% inc healing
`Player Bonus` (2 red + 2 green gems) -> +5% + 5% // (5 red + 5 green gems) -> +10% + 10% % // 20 green gems -> +15% + 15%
Maximum +35% inc damage and 35% inc healing
# Blue damage and healing (Dmg + BG)
Add 1 blue and 1 green gem
`Base` -> 5% inc blue damage and 5% inc healing
`Player Bonus` (2B + 2G gems) -> +5% + 5% // (5B + 5G gems) -> +10% + 10% % // (10B + 10G) gems -> +15% + 15%
Maximum +35% inc blue damage and 35% inc healing
### Increased GreenLife Combos ###
Generate by combining `Generic Spec (Basic GreenLife)` with respective RGB
# Increased % Red Life (Basic %HP + 2R)
Add 2 `red gems`
`Base` -> 10% inc red shield
`Player Bonus` 5 red gems -> +10% // 10 red gems -> +15% // 20 red gems -> +20%
Maximum +55% inc red shield
# Increased % Red Life and GreenLife (Basic %HP + 1R1G)
Add 1 red 1 green gem
`Base` -> 5% inc red shield and 5% inc hp
`Player Bonus` (2R + 2G gems) -> +5% + 5% // (5R + 5G gems) -> +10% + 10% % // (10R + 10G) gems -> +15% + 15%
Maximum +35% inc red shield and 35% inc hp
# Increased % Blue Life (Basic %HP + 2B)
Add 2 `blue gems`
`Base` -> 10% inc red shield
`Player Bonus` 5 blue gems -> +10% // 10 blue gems -> +15% // 20 blue gems -> +20%
Maximum +55% inc blue shield
# Increased % Blue Life and GreenLife (Basic %HP + 1B1G)
Add `1 blue and 1 green gems`
`Base` -> 5% inc red shield and 5% inc hp
`Player Bonus` (2B + 2G gems) -> +5% + 5% // (5B + 5G gems) -> +10% + 10% % // (10B + 10G) gems -> +15% + 15%
Maximum +35% inc blue shield and 35% inc hp
# Increased % GreenLife (Basic %HP + 2G)
Add `2 green gems`
`Base` -> 10% inc hp
`Player Bonus` 5 green gems -> +10% // 10 green gems -> +15% // 20 green gems -> +20%
Maximum +55% inc hp
# Increased % Blue and Red Life (Basic %HP + 1B1R)
Add `1 blue and 1 red gem`
`Base` -> 5% inc red shield and 5% inc hp
`Player Bonus` (2B + 2R gems) -> +5% + 5% // (5B + 5R gems) -> +10% + 10% % // (10B + 10R) gems -> +15% + 15%
Maximum +35% inc blue shield and 35% inc red shield
## Upgraded Attack Spec Combos
# Increased Strike Damage (Combine Strike + Red Damage Spec x 2)
Construct Requires `8 red gems`
Adds `6 red gems`
`Base` -> 15% increased strike damage
`Player Bonus` 15 red gems -> +15% // 20 red gems -> +20% // 30 red gems -> +30%
Maximum 80% increased strike damage
# Improved Heal (Combine Heal + Healing Spec x 2)
Construct Requires `8 green gems`
`Base` -> 15% increased heal healing
`Player Bonus` 15 green gems -> +15% // 20 green gems -> +20% // 30 green gems -> +30%
Maximum 80% increased heal healing
# Increased Blast Damage (Combine Blast + Blue Spec x 2)
Construct Requires `8 blue gems`
`Base` -> 15% increased blast damage
`Player Bonus` 15 blue gems -> +15% // 20 blue gems -> +20% // 30 blue gems -> +30%
Maximum 80% increased blast damage
# Increased Slay Damage (Combine Slay + Red Damage Spec + Healing Spec)
Construct Requires `4 red 4 green gems`
`Base` -> 15% increased slay damage
`Player Bonus` (8R + 8G) gems -> +15% // (10R + 10G) gems -> +20% // (15R + 15G) gems -> +30%
Maximum 80% increased slay damage
# Increased Banish Damage (Combine Slay + Red Damage Spec + Blue Damage Spec)
Construct Requires `4 red 4 blue gems`
`Base` -> 15% increased slay damage
`Player Bonus` (8R + 8B) gems -> +15% // (10R + 10B) gems -> +20% // (15R + 15B) gems -> +30%
Maximum 80% increased banish damage
## Other Combos
# Increased % Red Speed (Basic Speed + 2R)
Add 2 red gems
`Base` -> 15% inc red speed
`Player Bonus` 5 red gems -> +15% // 10 red gems -> +20% // 20 red gems -> +25%
Maximum 80% inc red speed
# Nature Affinity (Basic Class spec + 2R)
`Base` -> Add 10 red gems

View File

@ -1 +1 @@
1.10.0
1.11.0

View File

@ -3,7 +3,6 @@
_ntr_
* can't reset password without knowing password =\
* skip faceoff on server side
* change cooldowns to delay & recharge
- delay is cooldown before skill can first be used
- recharge is cooldown after using skill
@ -30,14 +29,11 @@ Hexagon Set
- Increase intensity for each visit
_mashy_
* floating combat text combat (start opposite hp and float towards it) to speed up animations
* rebalance
* speed specs
* life specs
* represent construct colours during game phase (try %bar or dots)
* buy from preview if you have the required bases in vbox / inventory
- a "buy" becomes available under the current info / preview section
- clicking the buy automatically purchases / combine items
- could also be used to upgrade already equipped skills / specs
- e.g. an equipped white power spec could be upgraded by clicking under preview
- if this was added we could reduce inventory size to 3 and rearrange vbox (see mockup img)
_external_
* Graphics
@ -57,29 +53,19 @@ _tba_
## SOON
* combo rework
- reduce number of items for creating t2/t3 items from 3 -> 2
- add lost complexity by adding skill spec items
- Created by combining a skill with corresponding spec
e.g.
- Strike + PowerRR -> StrikePower (Will be the power symbol with strike text under)
- Construct does Y% more damage with Strike
- Strike + SpeedRR -> StrikeSpeed (strike has Y% more speed)
- Strike + LifeRR -> StrikeLife (Strike recharges X% of damage as red life)
- Can also work as module style passive keystones
* troll life -> dmg -> Invert life spec?
* prince of peace
* bonus healing / no damage -> Heal power spec?
* fuck magic -> Some sort of reflect spec?
* empower on ko -> Amplify + Power spec
* Skill / Spec hybrids - SEE COMBOS.md
* elo + leaderboards
## LATER
* Graphical status effects instead of text
* buy from preview if you have the required bases in vbox / inventory
- a "buy" becomes available under the current info / preview section
- clicking the buy automatically purchases / combine items
- could also be used to upgrade already equipped skills / specs
- e.g. an equipped white power spec could be upgraded by clicking under preview
* theme toasts
* rework vecs into sets

View File

@ -1,6 +1,6 @@
{
"name": "mnml-client",
"version": "1.10.0",
"version": "1.11.0",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -10,6 +10,7 @@ echo "Setting version to $VERSION"
echo $VERSION | tr -d '\n' > VERSION
cd $MNML_PATH/server && sed -i "/^version/c\version = \"$VERSION\"" Cargo.toml
cd $MNML_PATH/core && sed -i "/^version/c\version = \"$VERSION\"" Cargo.toml
cd $MNML_PATH/ops && npm --allow-same-version --no-git-tag-version version "$VERSION"
cd $MNML_PATH/client && npm --allow-same-version --no-git-tag-version version "$VERSION"
cd $MNML_PATH/acp && npm --allow-same-version --no-git-tag-version version "$VERSION"

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 106 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -3,16 +3,25 @@
div {
padding-right: 1em;
// display: flex;
// flex-flow: column;
line-height: 2em;
}
h3 {
// text-transform: uppercase;
margin-bottom: 0.5em;
}
button {
width: 100%;
height: 2.5em;
display: block;
}
input {
width: 100%;
height: 3em;
height: 2.5em;
display: block;
}

View File

@ -5,7 +5,7 @@
@white: #f5f5f5; // whitesmoke
@purple: #9355b5; // 6lack - that far cover
@yellow: #ffa100;
@silver: #c0c0c0;
@silver: #2c2c2c;
@black: black;
@gray: #222;

View File

@ -54,11 +54,7 @@
button {
&.highlight {
color: black;
background: @silver;
// border: 1px solid @white; (this bangs around the vbox)
// overwrite the classes on white svg elements
svg {
stroke-width: 0.75em;
}

View File

@ -75,25 +75,11 @@
flex: 1;
border-top: 0;
border: 0.1em solid #222;
&:not(:last-child) {
border-right: 0;
}
&:last-child {
float: right;
}
}
}
.login {
display: flex;
flex-flow: column;
.terms {
display: inline;
margin: 0 1em;
}
}
}
section {
@ -108,62 +94,14 @@ section {
padding-right: 1em;
}
.list {
letter-spacing: 0.25em;
text-transform: uppercase;
display: grid;
// grid-template-columns: repeat(4, 1fr);
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
flex-flow: row wrap;
align-items: flex-end;
button {
border-radius: 0.25em;
// height: 3em;
}
&.sub {
grid-template-columns: 1fr;
}
&.play {
grid-template-columns: repeat(2, 1fr);
align-items: flex-start;
&.rejoin {
grid-template-columns: 1fr;
}
button.ready:enabled {
color: forestgreen;
border-color: forestgreen;
&:hover {
background: forestgreen;
color: black;
border-color: forestgreen;
}
}
// // all green
// button.ready:enabled {
// background: forestgreen;
// color: black;
// border-color: forestgreen;
// &:hover {
// color: forestgreen;
// border-color: forestgreen;
// background: 0;
// }
// }
}
}
.panes {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
}
.list {
margin-bottom: 2em;
figure {
letter-spacing: 0.25em;
@ -172,61 +110,92 @@ section {
display: flex;
flex-flow: column;
}
letter-spacing: 0.25em;
text-transform: uppercase;
display: grid;
// grid-template-columns: repeat(4, 1fr);
grid-template-columns: 1fr 1fr;
grid-gap: 1em;
flex-flow: row wrap;
align-items: flex-end;
&.sub {
grid-template-columns: 1fr;
}
&.play {
grid-template-columns: repeat(2, 1fr);
align-items: flex-start;
&.rejoin {
grid-template-columns: 1fr;
}
button.ready:enabled {
color: forestgreen;
border-color: forestgreen;
&:hover {
background: forestgreen;
color: black;
border-color: forestgreen;
}
}
// // all green
// button.ready:enabled {
// background: forestgreen;
// color: black;
// border-color: forestgreen;
// &:hover {
// color: forestgreen;
// border-color: forestgreen;
// background: 0;
// }
// }
}
}
.demo {
margin-top: 1em;
display: block;
.login {
display: flex;
flex-flow: column;
.terms {
display: inline;
margin: 0 1em;
}
button {
pointer-events: none;
padding: 0 0.5em;
margin-top: 1em;
}
}
.options {
grid-area: hdr;
display: flex;
.logo {
flex: 0 1 10%;
margin-right: 1em;
border: none;
}
section {
margin-bottom: 0.5em;
div:first-child {
padding-right: 1em;
}
}
.construct-section {
.construct-list {
height: 25em;
grid-area: unset;
.instance-construct {
// border: 0;
}
}
}
.colour-info {
grid-area: vinfo;
display: flex;
align-items: center;
div {
display: flex;
}
svg {
flex: 1;
height: 1em;
}
}
.game-demo {
.game {
height: 25em;
display: flex;
flex-flow: column;
.game-construct {
flex: 1;
}
button {
flex: 1;
border-top: 0;
border: 0.1em solid #222;
&:last-child {
float: right;
}
}
}
.intro {
text-align: center;
font-size: 0.8em;
}

View File

@ -27,23 +27,6 @@ html body {
overflow-y: hidden;
}
#mnml {
/* this is the sweet nectar to keep it full page*/
height: 100vh;
max-height: 100vh;
min-height: 100vh;
/* stops inspector going skitz*/
overflow-x: hidden;
// overflow-y: hidden;
}
// @media (min-width: 1921px) {
// html, body, #mnml {
// font-size: 16pt;
// }
// }
html {
box-sizing: border-box;
margin: 0;
@ -108,11 +91,37 @@ dl {
padding: 0.5em 1em;
/* this is the sweet nectar to keep it full page*/
height: 100vh;
max-height: 100vh;
min-height: 100vh;
/* stops inspector going skitz*/
overflow-x: hidden;
// overflow-y: hidden;
&.animations-test {
aside button {
font-size: 50%;
}
}
&.front-page {
display: block;
main {
padding: 0 25%;
}
.logo {
margin: 2em 0;
}
.list {
margin-bottom: 0;
}
}
}
main {
@ -129,7 +138,7 @@ button, input {
box-sizing: border-box;
font-size: 1em;
flex: 1;
border-radius: 0.5em;
border-radius: 0;
line-height: 2em;
padding-right: 0.1em;
padding-left: 0.1em;
@ -150,9 +159,12 @@ button, input {
&:focus {
/*colour necesary to bash skellington*/
outline: 0;
}
// &:active {
// filter: url("#noiseFilter");
// }
}
a {
@ -261,28 +273,12 @@ figure.gray {
display: none;
}
header {
.options {
font-size: 200%;
}
button {
height: 2em;
// border-radius: 0.1em;
border: none;
border-radius: 0;
}
}
.options {
button {
&.highlight {
color: @white;
box-shadow: inset 0px 5px 0px 0px @white;
border: 0;
}
border: none;
}
}
@ -300,11 +296,20 @@ li {
}
.logo {
height: 2em;
background-image: url("../../assets/mnml.logo.trim.svg");
height: 4em;
filter: url("#noiseFilter");
background-image: url("../../assets/mnml.logo.text.svg");
background-size: contain;
background-repeat: no-repeat;
background-position: left;
background-position: center;
}
.awards {
height: 100%;
background-image: url("../../assets/mnml.awards.svg");
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.discord-btn {
@ -316,8 +321,13 @@ li {
.mnni {
background-image: url("./../mnni.svg");
filter: url("#noiseFilter");
}
// .highlight {
// filter: url("#noiseFilter");
// }
.avatar {
grid-area: avatar;
object-fit: contain;
@ -328,6 +338,10 @@ li {
// pointer-events: none;
}
header {
// font-size: 1.2em;
}
#clipboard {
width: 1px;
height: 1px;
@ -359,4 +373,8 @@ li {
}
}
#noise {
height: 0;
}
@import 'styles.mobile.less';

View File

@ -7,6 +7,12 @@
font-size: 8pt;
padding: 0;
&.front-page {
main {
padding: 0 0.5em;
}
}
.instance {
grid-template-areas:
"vbox vbox"
@ -164,12 +170,21 @@
// portrait menu or small size vertical in landscape
@media (max-width: 550px) and (max-height: 800px) {
@media (max-width: 550px) and (max-height: 800px) and (orientation: portrait) {
#mnml {
grid-template-columns: 1fr;
grid-template-rows: 1fr;
grid-template-areas:
"main"
"main";
&.front-page {
display: block;
main {
padding: 0 0.5em;
}
}
}
section {
@ -264,6 +279,9 @@
}
.info-combiner {
max-height: 7em;
overflow-y: scroll;
.info {
display: none;
}

View File

@ -147,16 +147,11 @@
}
&.highlight {
color: black;
background: @silver;
// overwrite the classes on white svg elements
svg {
stroke-width: 0.75em;
}
.white {
stroke: black;
}
}
}

View File

@ -1,6 +1,4 @@
<!DOCTYPE html>
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><style>@font-face {
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><style>@font-face {
font-family: octicons-anchor;
src: url(https://cdnjs.cloudflare.com/ajax/libs/octicons/4.4.0/font/octicons.woff) format('woff');
}
@ -733,6 +731,24 @@ pre {
}
}
</style><title>CHANGELOG</title></head><body><article class="markdown-body"><h2>
<a id="user-content-1101---2019-12-04" class="anchor" href="#1101---2019-12-04" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.10.1] - 2019-12-04</h2>
<h3>
<a id="user-content-changed" class="anchor" href="#changed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>Reduced the number of items to create + and ++ versions from 3 to 2 (need 4 items total to make a ++)</li>
<li>Reduced the power of ++ skills to be closer to the power of previous + versions</li>
<li>Tweaked all values of power specs to reduce their power bonuses</li>
</ul>
<h2>
<a id="user-content-1100---2019-11-29" class="anchor" href="#1100---2019-11-29" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.10.0] - 2019-11-29</h2>
<h3>
<a id="user-content-changed-1" class="anchor" href="#changed-1" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>Reworked the vbox layout</li>
<li>Floating combat text and faster animations</li>
<li>Mobile UI fixes</li>
</ul>
<h2>
<a id="user-content-191---2019-11-21" class="anchor" href="#191---2019-11-21" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.9.1] - 2019-11-21</h2>
<h3>
<a id="user-content-fixed" class="anchor" href="#fixed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Fixed</h3>
@ -742,7 +758,7 @@ pre {
<h2>
<a id="user-content-190---2019-11-21" class="anchor" href="#190---2019-11-21" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.9.0] - 2019-11-21</h2>
<h3>
<a id="user-content-changed" class="anchor" href="#changed" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-2" class="anchor" href="#changed-2" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>VBOX
<ul>
@ -794,7 +810,7 @@ pre {
<li>Resizing of vbox when you buy / create certain items</li>
</ul>
<h3>
<a id="user-content-changed-1" class="anchor" href="#changed-1" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-3" class="anchor" href="#changed-3" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>Automatically shows a preview of combo item when you have 3 items selected for combining</li>
<li>Only highlight the first available item slot when equipping</li>
@ -826,7 +842,7 @@ pre {
<li>An issue where skills would not be put on cooldown after being used.</li>
</ul>
<h3>
<a id="user-content-changed-2" class="anchor" href="#changed-2" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-4" class="anchor" href="#changed-4" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>
<p>Game phase</p>
@ -893,7 +909,7 @@ pre {
</li>
</ul>
<h3>
<a id="user-content-changed-3" class="anchor" href="#changed-3" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-5" class="anchor" href="#changed-5" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>
<p>Construct life changed</p>
@ -979,7 +995,7 @@ pre {
</li>
</ul>
<h3>
<a id="user-content-changed-4" class="anchor" href="#changed-4" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-6" class="anchor" href="#changed-6" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>
<p>Vbox phase</p>
@ -1125,7 +1141,7 @@ pre {
<li>Player width styling</li>
</ul>
<h3>
<a id="user-content-changed-5" class="anchor" href="#changed-5" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-7" class="anchor" href="#changed-7" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>Improved wiggle animation</li>
<li>Intercept is now considered defensive by bots</li>
@ -1134,7 +1150,7 @@ pre {
<h2>
<a id="user-content-164---2019-10-24" class="anchor" href="#164---2019-10-24" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.6.4] - 2019-10-24</h2>
<h3>
<a id="user-content-changed-6" class="anchor" href="#changed-6" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-8" class="anchor" href="#changed-8" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>Animations processing on client side reduced.</li>
</ul>
@ -1176,7 +1192,7 @@ pre {
<li>Subscriber chat!</li>
</ul>
<h3>
<a id="user-content-changed-7" class="anchor" href="#changed-7" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-9" class="anchor" href="#changed-9" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>
<p>Made available skill / effect information during the combat phase.</p>
@ -1208,7 +1224,7 @@ pre {
<a id="user-content-156---2019-10-17" class="anchor" href="#156---2019-10-17" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.5.6] - 2019-10-17</h2>
<p>We've updated the UI during the vbox / buy phase to give a better indication of valid actions.</p>
<h3>
<a id="user-content-changed-8" class="anchor" href="#changed-8" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-10" class="anchor" href="#changed-10" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>
<p>Borders for skill combo's represent the base colours.</p>
@ -1234,7 +1250,7 @@ pre {
<h2>
<a id="user-content-155---2019-10-15" class="anchor" href="#155---2019-10-15" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>[1.5.5] - 2019-10-15</h2>
<h3>
<a id="user-content-changed-9" class="anchor" href="#changed-9" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<a id="user-content-changed-11" class="anchor" href="#changed-11" aria-hidden="true"><span aria-hidden="true" class="octicon octicon-link"></span></a>Changed</h3>
<ul>
<li>
<p>Purge</p>

View File

@ -1,6 +1,6 @@
{
"name": "mnml-client",
"version": "1.10.0",
"version": "1.11.0",
"description": "",
"main": "index.js",
"scripts": {

View File

@ -1,14 +1,12 @@
export const setAccount = value => ({ type: 'SET_ACCOUNT', value });
export const setAuthenticated = value => ({ type: 'SET_AUTHENTICATED', value });
export const setAnimating = value => ({ type: 'SET_ANIMATING', value });
export const setAnimCb = value => ({ type: 'SET_ANIM_CB', value });
export const setAnimFocus = value => ({ type: 'SET_ANIM_FOCUS', value });
export const setAnimSkill = value => ({ type: 'SET_ANIM_SKILL', value });
export const setAnimSkill = value => ({ type: 'SET_ANIM_SKILL', value });
export const setAnimSource = value => ({ type: 'SET_ANIM_SOURCE', value });
export const setAnimTarget = value => ({ type: 'SET_ANIM_TARGET', value });
export const setAnimText = value => ({ type: 'SET_ANIM_TEXT', value });
export const setDemo = value => ({ type: 'SET_DEMO', value });
export const setResolution = value => ({ type: 'SET_RESOLUTION', value });
export const setChatShow = value => ({ type: 'SET_CHAT_SHOW', value });
export const setChatWheel = value => ({ type: 'SET_CHAT_WHEEL', value });

View File

@ -4,9 +4,7 @@ const toast = require('izitoast');
const eachSeries = require('async/eachSeries');
const actions = require('./actions');
const { TIMES } = require('./constants');
const animations = require('./animations.utils');
const { removeTier } = require('./utils');
const { setAnimations, clearAnimations } = require('./animations.utils');
const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://mnml.gg/api/ws' : 'ws://localhost/api/ws';
@ -20,73 +18,32 @@ function createSocket(store) {
ws.send(cbor.encode(msg));
}
function sendDevResolve(a, b, skill) {
send(['DevResolve', { a, b, skill }]);
function sendDevResolve(skill) {
send(['DevResolve', { skill }]);
}
function onDevResolutions(newRes) {
const { game, account, animating } = store.getState();
if (animating) return false;
function setGame(game) {
store.dispatch(actions.setGame(game));
store.dispatch(actions.setAnimating(true));
store.dispatch(actions.setGameSkillInfo(null));
// stop fetching the game state til animations are done
const newRes = game.resolutions[game.resolutions.length - 1];
return eachSeries(newRes, (r, cb) => {
if (!r.event) return cb();
const timeout = animations.getTime(r.stages);
const anims = animations.getObjects(r, game, account);
const text = animations.getText(r);
store.dispatch(actions.setAnimFocus(animations.getFocusTargets(r, game)));
if (anims.animSkill) store.dispatch(actions.setAnimSkill(anims.animSkill));
if (r.stages.includes('START_SKILL') && anims.animSource) {
store.dispatch(actions.setAnimSource(anims.animSource));
}
if (r.stages.includes('END_SKILL') && anims.animTarget) {
store.dispatch(actions.setAnimTarget(anims.animTarget));
if (animations.isCbAnim(anims.animSkill)) store.dispatch(actions.setAnimCb(cb));
}
if (r.stages.includes('POST_SKILL') && text) {
// timeout to prevent text classes from being added too soon
if (timeout === TIMES.POST_SKILL_DURATION_MS) {
store.dispatch(actions.setAnimText(text));
} else {
setTimeout(
() => store.dispatch(actions.setAnimText(text)),
timeout - TIMES.POST_SKILL_DURATION_MS
);
}
}
return setTimeout(() => {
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimFocus([]));
if (r.stages.includes('END_SKILL') && animations.isCbAnim(anims.animSkill)) return true;
return cb();
}, timeout);
// if (r.delay === 0) return cb(); // TargetKo etc
setAnimations(r, store);
return setTimeout(cb, r.delay);
}, err => {
if (err) return console.error(err);
// clear animation state
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimating(false));
store.dispatch(actions.setGameEffectInfo(null));
clearAnimations(store);
// set the game state so resolutions don't fire twice
store.dispatch(actions.setGame(game));
// ws.sendGameState(game.id);
return true;
});
}
const handlers = {
DevResolutions: onDevResolutions,
GameState: setGame,
};
// decodes the cbor and

View File

@ -5,18 +5,7 @@ const { createStore, combineReducers } = require('redux');
const reducers = require('./reducers');
const actions = require('./actions');
const createSocket = require('./animations.socket');
// const TrippyTriangle = require('./components/svgs/trippy.triangle');
// const Amplify = require('./components/svgs/amplify');
// const Hex = require('./components/svgs/hex');
const Game = require('./components/game');
const testGameBuilder = require('./test.game');
const testGame = testGameBuilder('8552e0bf-340d-4fc8-b6fc-cccccccccccc');
const testAccount = {
id: '8552e0bf-340d-4fc8-b6fc-cccccccccccc',
name: 'ntr',
};
// Redux Store
const store = createStore(
@ -24,20 +13,15 @@ const store = createStore(
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
);
const testAccount = {
id: '8552e0bf-340d-4fc8-b6fc-cccccccccccc',
name: 'ntr',
};
store.dispatch(actions.setAccount(testAccount));
store.dispatch(actions.setGame(testGame));
function animationsNav(ws) {
function useSkill(skill) {
const ateam = Math.round(Math.random());
const bteam = Math.round(Math.random());
const acon = Math.floor(Math.random() * 3);
const bcon = Math.floor(Math.random() * 3);
const a = testGame.players[ateam].constructs[acon].id;
const b = testGame.players[bteam].constructs[bcon].id;
return ws.sendDevResolve(a, b, skill);
return ws.sendDevResolve(skill);
}
return SKILLS.map((s, i) => (

View File

@ -1,207 +1,49 @@
const actions = require('./actions');
const { TIMES } = require('./constants');
const { removeTier } = require('./utils');
function none() {
return {
animSource: null,
animTarget: null,
};
}
function setAnimations(r, store) {
const { focus, event: [type, variant] } = r;
function getObjects(resolution, game, account) {
if (!resolution) return none();
if (!resolution.target) return none();
if (type === 'HitAoe') {
const { construct } = variant;
const aoeFocus = focus.concat(construct);
const [type, event] = resolution.event;
if (!event || !event.skill) return none();
store.dispatch(actions.setResolution(null));
store.dispatch(actions.setAnimFocus(aoeFocus));
store.dispatch(actions.setAnimTarget(r));
const playerTeam = game.players.find(t => t.id === account.id);
const playerTeamIds = playerTeam.constructs.map(c => c.id);
const otherTeam = game.players.find(t => t.id !== account.id);
const otherTeamIds = otherTeam.constructs.map(c => c.id);
const sourceIsPlayer = playerTeamIds.includes(resolution.source.id);
const targetIsPlayer = playerTeamIds.includes(resolution.target.id);
const targetting = () => {
if (type === 'AoeSkill') {
if (targetIsPlayer) return playerTeamIds;
return otherTeamIds;
}
return [resolution.target.id];
};
const sameTeam = (sourceIsPlayer && targetIsPlayer) || (!sourceIsPlayer && !targetIsPlayer);
let y = 0;
if (!sameTeam) y = targetIsPlayer ? 1 : -1;
const i = sourceIsPlayer
? playerTeamIds.findIndex(c => c === resolution.source.id)
: otherTeamIds.findIndex(c => c === resolution.source.id);
const j = targetIsPlayer
? playerTeamIds.findIndex(c => c === resolution.target.id)
: otherTeamIds.findIndex(c => c === resolution.target.id);
const x = j - i;
const direction = { x, y };
// const targetTeam = targetIsPlayer ? playerTeamIds : otherTeamIds;
const createSourceAnim = () => {
return {
animation: 'sourceCast',
constructId: resolution.source.id,
direction,
};
};
const skipSource = !resolution.stages.includes('START_SKILL')
|| resolution.source.id === resolution.target.id;
const animSource = skipSource
? null
: createSourceAnim();
const animTarget = {
skill: event.skill,
constructId: targetting(),
player: playerTeamIds.includes(resolution.target.id),
direction,
};
return {
animSource,
animTarget,
animSkill: event.skill,
};
}
function getTime(stages) {
let time = 0;
if (stages.includes('START_SKILL') && stages.includes('END_SKILL')) {
time += TIMES.SOURCE_AND_TARGET_TOTAL_DURATION;
} else {
if (stages.includes('START_SKILL')) time += TIMES.SOURCE_DURATION_MS;
if (stages.includes('END_SKILL')) time += TIMES.TARGET_DURATION_MS;
}
if (stages.includes('POST_SKILL')) time += TIMES.POST_SKILL_DURATION_MS;
return time;
}
function getFocusTargets(resolution, game) {
if (!resolution) return [];
if (!resolution.event) return [];
const [type] = resolution.event;
const source = resolution.source.id;
const target = resolution.target.id;
if (type === 'AoeSkill') {
const targetTeam = game.players.find(t => t.constructs.find(c => c.id === target));
const targetTeamIds = targetTeam.constructs.map(c => c.id);
if (source !== target) targetTeamIds.push(source);
return targetTeamIds;
}
if (source !== target) return [source, target];
return [target];
}
function getText(resolution) {
const nullText = { text: null, constructId: null, life: null };
if (!resolution) return nullText;
if (!resolution.stages.includes('POST_SKILL')) return nullText;
function generatePostSkill() {
const [type, event] = resolution.event;
if (type === 'Ko') {
return { text: 'KO!', css: 'ko-transition' };
}
if (type === 'Disable') {
const { disable } = event;
return { text: `${disable}`, css: '' };
}
if (type === 'Immunity') {
return { text: 'IMMUNE', css: '' };
}
if (type === 'Damage') {
const { mitigation, colour } = event;
let { amount } = event;
let css = '';
if (colour === 'Green') {
css = 'green';
amount *= -1;
}
if (colour === 'Red') css = 'red';
if (colour === 'Blue') css = 'blue';
const mitigationText = mitigation
? `(${mitigation})`
: '';
return { text: `${amount} ${mitigationText}`, css };
}
if (type === 'Healing') {
const { amount, overhealing } = event;
return { text: `${amount} (${overhealing} OH)`, css: 'green' };
}
if (type === 'Inversion') {
return { text: 'INVERT', css: '' };
}
if (type === 'Reflection') {
return { text: 'REFLECT', css: '' };
}
if (type === 'Effect') {
const { effect, duration, construct_effects: effects } = event;
return { text: `+ ${effect} ${duration}T`, css: '', effects };
}
if (type === 'Recharge') {
const { red, blue } = event;
if (red > 0 && blue > 0) return { text: `+${red}R +${blue}B`, css: 'rb' };
if (red > 0) return { text: `+${red}R`, css: 'red' };
if (blue > 0) return { text: `+${blue}B`, css: 'blue' };
return nullText;
}
if (type === 'Removal') {
const { effect, construct_effects: effects } = event;
if (!effect) {
return { text: 'Effect Removal', css: '', effects };
}
return { text: `-${effect}`, css: '', effects };
}
return false;
return setTimeout(() => store.dispatch(actions.setAnimSource(null)), TIMES.SOURCE_DURATION_MS);
}
const { green, red, blue } = resolution.target;
const { text, css, effects } = generatePostSkill();
const skill = resolution.event[1] ? resolution.event[1].skill : null;
return {
css,
text,
effects,
life: { green, red, blue },
constructId: resolution.target.id,
skill,
};
store.dispatch(actions.setAnimFocus(focus));
if (type === 'Cast') {
store.dispatch(actions.setResolution(null));
store.dispatch(actions.setAnimSource(r));
return setTimeout(() => store.dispatch(actions.setAnimSource(null)), TIMES.SOURCE_DURATION_MS);
}
if (type === 'Hit') {
store.dispatch(actions.setResolution(null));
store.dispatch(actions.setAnimTarget(r));
return setTimeout(() => store.dispatch(actions.setAnimTarget(null)), TIMES.TARGET_DURATION_MS);
}
return store.dispatch(actions.setResolution(r));
}
function isCbAnim(skill) {
return ['Attack', 'Blast', 'Siphon', 'SiphonTick', 'Strike', 'Chaos', 'Slay', 'Heal',
'Buff', 'Amplify', 'Haste', 'Triage', 'TriageTick', 'Link', 'Hybrid', 'Intercept',
'Debuff', 'Curse', 'Decay', 'DecayTick', 'Purge', 'Silence', 'Restrict',
'Stun', 'Bash', 'Absorb', 'Sleep', 'Break', 'Ruin',
'Block', 'Sustain', 'Electrify', 'Electrocute', 'ElectrocuteTick',
'Counter', 'CounterAttack', 'Purify', 'Recharge', 'Reflect'].includes(removeTier(skill));
function clearAnimations(store) {
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
store.dispatch(actions.setResolution(null));
store.dispatch(actions.setAnimating(false));
store.dispatch(actions.setGameEffectInfo(null));
store.dispatch(actions.setAnimFocus(null));
}
module.exports = {
getFocusTargets,
getObjects,
getTime,
getText,
isCbAnim,
setAnimations,
clearAnimations,
};

View File

@ -33,6 +33,10 @@ const ws = createSocket(events);
ws.connect();
events.setWs(ws);
if (process.env.NODE_ENV !== 'development') {
LogRocket.init('yh0dy3/mnml');
}
const App = () => (
<Provider store={store}>
{window.Stripe

View File

@ -152,11 +152,9 @@ class AccountStatus extends Component {
return (
<section class='account top' onClick={tlClick}>
{subInfo()}
<div>
{subInfo()}
</div>
<div>
<label for="email">Email Settings:</label>
<h3>Email</h3>
<dl>
<dt>Recovery Email</dt>
<dd>{email ? email.email : 'No email set'}</dd>
@ -174,6 +172,7 @@ class AccountStatus extends Component {
<button onClick={() => sendSetEmail(emailState)}>Update</button>
</div>
<div>
<h3>Password</h3>
<label for="current">Password:</label>
<input
class="login-input"
@ -208,6 +207,7 @@ class AccountStatus extends Component {
</button>
</div>
<div>
<h3>Other</h3>
<figure>
<figcaption>spawn new construct</figcaption>
<button onClick={() => sendConstructSpawn()} type="submit">

View File

@ -5,6 +5,7 @@ const { connect } = require('preact-redux');
const Amplify = require('./anims/amplify');
const Attack = require('./anims/attack');
const Absorb = require('./anims/absorb');
const Absorption = require('./anims/absorption');
const Bash = require('./anims/bash');
const Blast = require('./anims/blast');
const Block = require('./anims/block');
@ -44,8 +45,8 @@ const { removeTier } = require('../utils');
const addState = connect(
function receiveState(state) {
const { animTarget } = state;
return { animTarget };
const { animTarget, account } = state;
return { animTarget, account };
}
);
@ -60,20 +61,21 @@ class ConstructAnimation extends Component {
const {
animTarget,
construct,
account,
} = props;
if (!animTarget) return false;
const {
skill,
player,
direction,
constructId,
} = animTarget;
const { skill, event: [, variant] } = animTarget;
const { construct: constructId, player, direction: [x, y] } = variant;
const animY = y && player === account.id ? -1 : y;
const isPlayer = player === account.id;
const direction = { x, y: animY };
const animSkill = removeTier(skill);
if (!constructId.includes(construct.id)) return false;
// find target animation
const chooseAnim = () => {
switch (animSkill) {
@ -93,9 +95,9 @@ class ConstructAnimation extends Component {
case 'Haste': return <Haste />;
case 'Triage': return <Triage />;
case 'TriageTick': return <TriageTick />;
case 'Link': return <Link player={player} />;
case 'Link': return <Link player={isPlayer} />;
case 'Hybrid': return <Hybrid />;
case 'Intercept': return <Intercept player={player} />;
case 'Intercept': return <Intercept player={isPlayer} />;
// Debuff base
case 'Debuff': return <Debuff />;
@ -112,21 +114,22 @@ class ConstructAnimation extends Component {
// case 'Banish': return setAvatarAnimation(true, true, resolution.id, construct.id, 'banish', null);
case 'Bash': return <Bash />;
case 'Absorb': return <Absorb />;
case 'Absorption': return <Absorption />;
case 'Sleep': return <Sleep />;
case 'Break': return <Break />;
case 'Ruin': return <Ruin />;
// Block Base
case 'Block': return <Block />;
case 'Sustain': return <Sustain player={player} />;
case 'Sustain': return <Sustain player={isPlayer} />;
case 'Electrify': return <Electrify />;
case 'Electrocute': return <Electrocute />;
case 'ElectrocuteTick': return <Electrocute />;
case 'Counter': return <Counter player={player} />;
case 'Counter': return <Counter player={isPlayer} />;
case 'CounterAttack': return <Attack direction={direction} />;
case 'Purify': return <Purify player={player} />;
case 'Recharge': return <Recharge player={player} />;
case 'Reflect': return <Refl player={player} />;
case 'Purify': return <Purify player={isPlayer} />;
case 'Recharge': return <Recharge player={isPlayer} />;
case 'Reflect': return <Refl player={isPlayer} />;
default: return false;
}

View File

@ -1,21 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
// shamelessly lifted from teh anime docs
// https://animejs.com/documentation/#svgAttr
class Absorb extends Component {
constructor() {
super();
@ -56,7 +44,7 @@ class Absorb extends Component {
this.animations.push(anime({
targets: ['#absorb'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -66,8 +54,6 @@ class Absorb extends Component {
targets: ['#absorb polygon'],
points: '64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788',
easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -76,8 +62,6 @@ class Absorb extends Component {
strokeWidth: [2, 1],
easing: 'easeInOutSine',
direction: 'alternate',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -90,8 +74,7 @@ class Absorb extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Absorb);
module.exports = Absorb;

View File

@ -0,0 +1,80 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
class Absorb extends Component {
constructor() {
super();
this.animations = [];
}
render() {
return (
<svg
class='skill-animation blue'
version="1.1"
id="absorb"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 128 128">
<defs>
<filter id="absorbFilter">
<feGaussianBlur in="SourceGraphic" stdDeviation="5" />
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<polygon
class='blue'
points="64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788"
filter="url(#absorbFilter)">
</polygon>
<polygon
class='white'
points="64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788">
</polygon>
</svg>
);
}
componentDidMount() {
this.animations.push(anime({
targets: ['#absorb'],
opacity: [
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
}));
this.animations.push(anime({
targets: ['#absorb polygon'],
points: '64 68.64 8.574 100 63.446 67.68 64 4 64.554 67.68 119.426 100',
easing: 'easeOutExpo',
duration: TIMES.TARGET_DURATION_MS,
}));
this.animations.push(anime({
targets: ['#absorb polygon.white'],
strokeWidth: [2, 1],
easing: 'easeInOutSine',
direction: 'alternate',
duration: TIMES.TARGET_DURATION_MS,
}));
}
// this is necessary because
// skipping / timing / unmounting race conditions
// can cause the animations to cut short, this will ensure the values are reset
// because preact will recycle all these components
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
}
}
module.exports = Absorb;

View File

@ -5,13 +5,6 @@ const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Amplify extends Component {
constructor() {
super();
@ -43,7 +36,7 @@ class Amplify extends Component {
this.animations.push(anime({
targets: ['#amplify'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -54,8 +47,6 @@ class Amplify extends Component {
d: [{ value: altPath }],
stroke: ['#3050f8', '#a52a2a'],
easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -64,8 +55,6 @@ class Amplify extends Component {
baseFrequency: 0.15,
scale: 4,
easing: 'easeInOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -78,8 +67,7 @@ class Amplify extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Amplify);
module.exports = Amplify;

View File

@ -5,13 +5,6 @@ const anime = require('animejs').default;
const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Attack extends Component {
constructor(props) {
super();
@ -46,8 +39,8 @@ class Attack extends Component {
y: [400, 200],
height: [100, 10, 0],
width: [12, 5, 0],
delay: () => anime.random(TIMES.TARGET_DELAY_MS, TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS / 2),
duration: TIMES.TARGET_DURATION_MS,
delay: () => anime.random(0, TIMES.TARGET_DURATION_MS / 4),
duration: TIMES.TARGET_DURATION_MS * 5 / 4,
}));
}
@ -59,9 +52,7 @@ class Attack extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Attack);
module.exports = Attack;

View File

@ -8,7 +8,6 @@ function Banish(id, idle) {
scaleY: 0,
fill: '#fff',
easing: 'easeOutElastic',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.45,
direction: 'alternate',
begin: idle.pause,

View File

@ -5,13 +5,6 @@ const anime = require('animejs').default;
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Bash extends Component {
constructor() {
super();
@ -54,7 +47,7 @@ class Bash extends Component {
this.animations.push(anime({
targets: ['#bash'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -64,7 +57,6 @@ class Bash extends Component {
targets: ['#bash'],
scale: {
value: 1,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeInExpo',
},
@ -72,10 +64,9 @@ class Bash extends Component {
value: 180,
easing: 'linear',
loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
},
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.1,
delay: TIMES.TARGET_DURATION_MS * 0.1,
duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeOutSine',
}));
@ -86,8 +77,6 @@ class Bash extends Component {
scale: 10,
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -100,8 +89,7 @@ class Bash extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Bash);
module.exports = Bash;

View File

@ -1,18 +1,10 @@
const preact = require('preact');
const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Bash extends Component {
constructor() {
super();
@ -47,7 +39,7 @@ class Bash extends Component {
this.animations.push(anime({
targets: ['#bash'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -57,7 +49,6 @@ class Bash extends Component {
targets: ['#bash'],
scale: {
value: 1,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeInExpo',
},
@ -74,7 +65,7 @@ class Bash extends Component {
{ translateX: 0, translateY: 2 },
],
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.1,
delay: TIMES.TARGET_DURATION_MS * 0.1,
duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeOutSine',
}));
@ -90,7 +81,6 @@ class Bash extends Component {
],
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -103,8 +93,7 @@ class Bash extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Bash);
module.exports = Bash;

View File

@ -1,34 +1,12 @@
const preact = require('preact');
const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const times = require('lodash/times');
const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
cx={x}
cy={y}
r={radius}
fill="url(#grad1)"
stroke-width="2"
stroke={colour}
filter="url(#explosion)"
/>
);
}
class Blast extends Component {
constructor(props) {
constructor() {
super();
this.animations = [];
}
@ -62,7 +40,7 @@ class Blast extends Component {
this.animations.push(anime({
targets: ['#blast'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.5, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -75,7 +53,6 @@ class Blast extends Component {
`,
style: { rotate: anime.random(-180, 180) },
easing: 'easeOutCubic',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -88,8 +65,7 @@ class Blast extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Blast);
module.exports = Blast;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Block extends Component {
constructor() {
super();
@ -38,7 +30,7 @@ class Block extends Component {
this.animations.push(anime({
targets: ['#block'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -53,8 +45,7 @@ class Block extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Block);
module.exports = Block;

View File

@ -1,20 +1,12 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
// logarithmic spiral lifted from
// https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Break extends Component {
constructor() {
super();
@ -63,7 +55,7 @@ class Break extends Component {
this.animations.push(anime({
targets: ['#break'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -74,7 +66,6 @@ class Break extends Component {
rotate: 180,
easing: 'linear',
loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -82,7 +73,6 @@ class Break extends Component {
targets: ['#break circle'],
easing: 'easeInSine',
r: 300,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -93,10 +83,8 @@ class Break extends Component {
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
// this is necessary because
@ -107,8 +95,7 @@ class Break extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Break);
module.exports = Break;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Buff extends Component {
constructor() {
super();
@ -39,7 +31,7 @@ class Buff extends Component {
this.animations.push(anime({
targets: ['#buff'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -50,7 +42,6 @@ class Buff extends Component {
points: '0,190 100,10 190,190',
easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -59,7 +50,6 @@ class Buff extends Component {
points: '40,170 100,50 160,170',
easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -68,7 +58,6 @@ class Buff extends Component {
points: '70,150 100,90 130,150',
easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -81,8 +70,7 @@ class Buff extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Buff);
module.exports = Buff;

View File

@ -1,26 +1,19 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const { randomPoints } = require('../../utils');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
cx={x}
cy={y}
cx={anime.random(0, 400)}
cy={anime.random(0, 400)}
r={radius}
fill={colour}
stroke="none"
stroke={colour === '#a52a2a' ? 'none' : '#f5f5f5'}
stroke-width={colour === '#a52a2a' ? '0' : '0.05em'}
filter={colour === '#a52a2a' ? 'url(#chaosRedFilter)' : 'url(#chaosBlueFilter)'}
/>
);
@ -30,10 +23,10 @@ class Chaos extends Component {
constructor() {
super();
this.animations = [];
const points = randomPoints(20, 30, { x: 0, y: 0, width: 300, height: 100 });
const points = randomPoints(20, 30, { x: 0, y: 0, width: 1000, height: 1000 });
this.charges = points.map(coord => {
const colour = Math.random() >= 0.5 ? '#a52a2a' : '#3050f8';
return projectile(coord[0], coord[1], 14, colour);
return projectile(coord[0], coord[1], '0.5em', colour);
});
}
@ -65,39 +58,12 @@ class Chaos extends Component {
componentDidMount() {
const projectiles = document.querySelectorAll('.skill-anim circle');
anime.set('.skill-anim', {
translateY: -(window.innerHeight) * 0.35 * this.props.direction.y,
translateX: -(window.innerWidth) * 0.15 * this.props.direction.x,
opacity: 0,
});
this.animations.push(anime({
targets: '.skill-anim',
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.3 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.POST_SKILL_DURATION_MS },
],
}));
this.animations.push(anime({
targets: '.skill-anim',
translateY: 0,
translateX: 0,
loop: false,
delay: TIMES.TARGET_DELAY_MS,
duration: (TIMES.TARGET_DURATION_MS * 1 / 2),
easing: 'easeInQuad',
}));
this.animations.push(anime({
targets: ['#chaosRedFilter feTurbulence', '#chaosRedFilter feDisplacementMap'],
baseFrequency: 2,
scale: 20,
numOctaves: 5,
scale: 5,
numOctaves: 3,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -105,13 +71,9 @@ class Chaos extends Component {
targets: proj,
cx: 150 + (Math.random() * 50 * (Math.random() < 0.5 ? -1 : 1)),
cy: 200 + (Math.random() * 50 * (Math.random() < 0.5 ? -1 : 1)),
// cx: 150,
// cy: 200,
// opacity: 0,
delay: TIMES.TARGET_DELAY_MS,
duration: (TIMES.TARGET_DURATION_MS * 2 / 3),
easing: 'easeInQuad',
duration: anime.random(TIMES.TARGET_DURATION_MS * 2 / 3, TIMES.TARGET_DURATION_MS),
opacity: 0,
easing: 'easeInExpo',
})));
}
@ -119,8 +81,7 @@ class Chaos extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Chaos);
module.exports = Chaos;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Counter extends Component {
constructor() {
super();
@ -51,7 +43,7 @@ class Counter extends Component {
this.animations.push(anime({
targets: ['#counter'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -60,7 +52,7 @@ class Counter extends Component {
this.animations.push(anime({
targets: ['#counter'],
rotateX: 180,
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS / 3,
delay: TIMES.TARGET_DURATION_MS / 3,
duration: TIMES.TARGET_DURATION_MS / 2,
easing: 'easeOutSine',
}));
@ -71,8 +63,6 @@ class Counter extends Component {
scale: 10,
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -85,8 +75,7 @@ class Counter extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Counter);
module.exports = Counter;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Curse extends Component {
constructor() {
super();
@ -54,7 +46,7 @@ class Curse extends Component {
this.animations.push(anime({
targets: ['#curse'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -64,7 +56,6 @@ class Curse extends Component {
targets: ['#curseCircleOne', '#curseFilterOne'],
r: 30,
easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -72,7 +63,6 @@ class Curse extends Component {
targets: ['#curseCircleTwo', '#curseFilterTwo'],
r: 60,
easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -80,7 +70,6 @@ class Curse extends Component {
targets: ['#curseCircleThree', '#curseFilterThree'],
r: 90,
easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -93,8 +82,7 @@ class Curse extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Curse);
module.exports = Curse;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Debuff extends Component {
constructor() {
super();
@ -41,7 +33,7 @@ class Debuff extends Component {
this.animations.push(anime({
targets: ['#debuff'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -52,7 +44,6 @@ class Debuff extends Component {
points: '0,190 100,10 190,190',
easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -61,7 +52,6 @@ class Debuff extends Component {
points: '40,170 100,50 160,170',
easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -70,7 +60,6 @@ class Debuff extends Component {
points: '70,150 100,90 130,150',
easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -83,8 +72,7 @@ class Debuff extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Debuff);
module.exports = Debuff;

View File

@ -2,17 +2,9 @@ const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const times = require('lodash/times');
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Decay extends Component {
constructor() {
super();
@ -40,7 +32,7 @@ class Decay extends Component {
this.animations.push(anime({
targets: ['#decay'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -55,7 +47,6 @@ class Decay extends Component {
rotate(${anime.random(-15, 15)})
`,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -68,8 +59,7 @@ class Decay extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Decay);
module.exports = Decay;

View File

@ -1,19 +1,10 @@
const preact = require('preact');
const { Component } = require('preact');
const times = require('lodash/times');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Electrify extends Component {
constructor() {
super();
@ -57,7 +48,7 @@ class Electrify extends Component {
this.animations.push(anime({
targets: ['#electrify'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -109,8 +100,7 @@ class Electrify extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Electrify);
module.exports = Electrify;

View File

@ -1,19 +1,11 @@
const preact = require('preact');
const { Component } = require('preact');
const times = require('lodash/times')
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Electrocute extends Component {
constructor() {
super();
@ -55,7 +47,7 @@ class Electrocute extends Component {
this.animations.push(anime({
targets: ['#electrify'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -101,8 +93,7 @@ class Electrocute extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Electrocute);
module.exports = Electrocute;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Haste extends Component {
constructor() {
super();
@ -49,7 +41,7 @@ class Haste extends Component {
this.animations.push(anime({
targets: ['#haste'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -59,7 +51,6 @@ class Haste extends Component {
targets: ['#haste g'],
stroke: [COLOURS.GREEN, COLOURS.RED],
easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.75,
}));
@ -69,8 +60,6 @@ class Haste extends Component {
scale: 10,
numOctaves: 5,
easing: 'easeInOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -83,8 +72,7 @@ class Haste extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Haste);
module.exports = Haste;

View File

@ -1,18 +1,10 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const { randomPoints } = require('../../utils');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
@ -60,7 +52,7 @@ class Heal extends Component {
this.animations.push(anime({
targets: ['#heal'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS / 4, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -70,8 +62,7 @@ class Heal extends Component {
targets: ['#heal circle'],
cx: 150,
cy: 200,
delay: TIMES.TARGET_DELAY_MS * 4,
duration: TIMES.TARGET_DURATION_MS * 0.9,
duration: TIMES.TARGET_DURATION_MS * 0.6,
easing: 'easeOutCirc',
direction: 'reverse',
}));
@ -81,8 +72,7 @@ class Heal extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Heal);
module.exports = Heal;

View File

@ -1,16 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
// shamelessly lifted from teh anime docs
// https://animejs.com/documentation/#svgAttr
@ -54,7 +47,7 @@ class Hex extends Component {
this.animations.push(anime({
targets: ['#hex'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -65,7 +58,6 @@ class Hex extends Component {
points: '64 124.94732621931382 8.574 96.00354944613788 8.62269130211947 32.03166105954991 64 4 119.37730869788052 32.03166105954991 119.426 96.00354944613788',
easing: 'easeOutExpo',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -75,7 +67,6 @@ class Hex extends Component {
easing: 'easeInOutSine',
direction: 'alternate',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -88,8 +79,7 @@ class Hex extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Hex);
module.exports = Hex;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Hybrid extends Component {
constructor() {
super();
@ -94,12 +86,11 @@ class Hybrid extends Component {
this.animations.push(anime({
targets: ['#hybrid'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
transform: {
value: ['rotate(0)', 'rotate(360)'],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
direction: 'alternate',
},
@ -110,7 +101,6 @@ class Hybrid extends Component {
r: [10, anime.random(10, 30)],
targets: ['#hybrid circle.green-one'],
cx: [50, 250, 50, 250],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutSine',
loop: true,
@ -120,7 +110,6 @@ class Hybrid extends Component {
r: [10, anime.random(10, 30)],
targets: ['#hybrid circle.green-two'],
cy: [250, 50, 250, 50],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutSine',
loop: true,
@ -131,7 +120,6 @@ class Hybrid extends Component {
targets: ['#hybrid circle.bluewhite-one'],
fill: [COLOURS.WHITE, COLOURS.BLUE],
cy: [50, 250, 50, 250],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutSine',
loop: true,
@ -142,7 +130,6 @@ class Hybrid extends Component {
targets: ['#hybrid circle.bluewhite-two'],
cx: [250, 50, 250, 50],
fill: [COLOURS.WHITE, COLOURS.BLUE],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutSine',
loop: true,
@ -153,8 +140,7 @@ class Hybrid extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Hybrid);
module.exports = Hybrid;

View File

@ -1,7 +1,5 @@
const preact = require('preact');
const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const {
@ -9,13 +7,6 @@ const {
COLOURS,
} = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Intercept extends Component {
constructor() {
super();
@ -47,7 +38,7 @@ class Intercept extends Component {
this.animations.push(anime({
targets: ['#intercept'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -61,7 +52,6 @@ class Intercept extends Component {
],
strokeWidth: 0,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInCubic',
// direction: 'reverse',
@ -70,7 +60,6 @@ class Intercept extends Component {
this.animations.push(anime({
targets: ['#intercept rect'],
y: 96,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInCubic',
// direction: 'reverse',
@ -82,8 +71,6 @@ class Intercept extends Component {
scale: 10,
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -96,8 +83,7 @@ class Intercept extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Intercept);
module.exports = Intercept;

View File

@ -6,7 +6,6 @@ function Invert(id, idle) {
return anime({
targets: [document.getElementById(id)],
rotate: 180,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.45,
easing: 'easeInOutElastic',
direction: 'alternate',

View File

@ -1,18 +1,10 @@
const preact = require('preact');
const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Link extends Component {
constructor() {
super();
@ -68,7 +60,7 @@ class Link extends Component {
this.animations.push(anime({
targets: ['#link'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: 1000 },
{ value: 1, duration: 1000 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -78,7 +70,6 @@ class Link extends Component {
targets: ['#link path'],
strokeDashoffset: [anime.setDashoffset, 0],
duration: TIMES.TARGET_DURATION_MS * 0.8,
delay: TIMES.TARGET_DELAY_MS,
easing: 'easeInOutSine',
});
}
@ -91,8 +82,7 @@ class Link extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Link);
module.exports = Link;

View File

@ -1,17 +1,8 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Purge extends Component {
constructor() {
super();
@ -51,7 +42,7 @@ class Purge extends Component {
this.animations.push(anime({
targets: ['#purge'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -60,7 +51,6 @@ class Purge extends Component {
this.animations.push(anime({
targets: ['#purge g'],
strokeWidth: [15, 0],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInSine',
}));
@ -81,8 +71,7 @@ class Purge extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Purge);
module.exports = Purge;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
@ -80,7 +72,7 @@ class Purify extends Component {
this.animations.push(anime({
targets: ['#purify'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -89,7 +81,7 @@ class Purify extends Component {
this.animations.push(anime({
targets: ['#block'],
opacity: [
{ value: 0, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS },
{ value: 0, duration: TIMES.TARGET_DURATION_MS },
],
easing: 'easeInOutSine',
}));
@ -102,7 +94,6 @@ class Purify extends Component {
easing: 'easeInOutSine',
cx: 128,
cy: 24,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -115,8 +106,7 @@ class Purify extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Purify);
module.exports = Purify;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Recharge extends Component {
constructor() {
super();
@ -59,7 +51,7 @@ class Recharge extends Component {
this.animations.push(anime({
targets: ['#recharge'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -71,7 +63,6 @@ class Recharge extends Component {
easing: 'easeInOutSine',
direction: 'alternate',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -82,7 +73,6 @@ class Recharge extends Component {
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -95,8 +85,7 @@ class Recharge extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Recharge);
module.exports = Recharge;

View File

@ -1,83 +0,0 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Block extends Component {
constructor() {
super();
this.animations = [];
}
render() {
return (
<svg
class="skill-animation red"
version="1.1"
id="block"
xmlns="http://www.w3.org/2000/svg"
style={{ transform: 'rotate(180deg)' }}
viewBox="0 0 256 256">
<filter id='blockFilter'>
<feTurbulence type="turbulence" baseFrequency="0.05" numOctaves="2" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="15" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
</filter>
<polygon
points='128,168 80,240 176,240'
style={{ filter: 'url("#blockFilter")' }}
id="charge"/>
<polyline
points='176,240 212,216 128,96 44,216 80,240'
style={{ filter: 'url("#blockFilter")' }}
id="charge"/>
<polyline
points='212,216 248,192 128,24 8,192 44,216'
style={{ filter: 'url("#blockFilter")' }}
id="charge"/>
</svg>
);
}
componentDidMount() {
this.animations.push(anime({
targets: ['#block'],
opacity: [
{ value: 1, delay: TIMES.TARGET_FADE_IN_DELAY, duration: TIMES.TARGET_FADE_IN_DURATION },
{ value: 0, delay: TIMES.TARGET_FADE_OUT_DELAY, duration: TIMES.FADE_OUT_DURATION },
],
easing: 'easeInOutSine',
}));
this.animations.push(anime({
targets: ['#blockFilter feTurbulence', ' #blockFilter feDisplacementMap'],
baseFrequency: 0,
scale: 1,
easing: 'easeOutSine',
delay: TIMES.TARGET_FADE_IN_DELAY,
duration: TIMES.TARGET_DURATION_MS,
}));
}
// this is necessary because
// skipping / timing / unmounting race conditions
// can cause the animations to cut short, this will ensure the values are reset
// because preact will recycle all these components
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Block);

View File

@ -1,89 +0,0 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Intercept extends Component {
constructor() {
super();
this.animations = [];
}
render({ player }) {
return (
<svg
class="skill-animation red"
version="1.1"
id="intercept"
xmlns="http://www.w3.org/2000/svg"
style={{
transform: player ? 'rotate3d(1, 0, 0, 180deg)' : '',
}}
viewBox="0 0 128 128">
<filter id='interceptFilter'>
<feTurbulence type="turbulence" baseFrequency="0" numOctaves="1" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
</filter>
<g filter="url(#interceptFilter)">
<circle cx="64" cy="128" r="48" />
<circle cx="64" cy="128" r="32" />
<circle cx="64" cy="128" r="16" />
</g>
</svg>
);
}
componentDidMount() {
this.animations.push(anime({
targets: ['#intercept'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
}));
this.animations.push(anime({
targets: ['#intercept'],
scale: 3,
strokeWidth: 0,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInOutCubic',
}));
this.animations.push(anime({
targets: ['#interceptFilter feTurbulence', '#interceptFilter feDisplacementMap'],
baseFrequency: 2,
scale: 10,
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
// this is necessary because
// skipping / timing / unmounting race conditions
// can cause the animations to cut short, this will ensure the values are reset
// because preact will recycle all these components
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Intercept);

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Refl extends Component {
constructor() {
super();
@ -54,7 +46,7 @@ class Refl extends Component {
this.animations.push(anime({
targets: ['#reflect'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -77,8 +69,7 @@ class Refl extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Refl);
module.exports = Refl;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Restrict extends Component {
constructor() {
super();
@ -42,7 +34,7 @@ class Restrict extends Component {
this.animations.push(anime({
targets: ['#restrict'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -52,12 +44,11 @@ class Restrict extends Component {
targets: ['#restrict'],
scale: {
value: 1,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeInExpo',
},
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.1,
delay: TIMES.TARGET_DURATION_MS * 0.1,
duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeOutSine',
}));
@ -69,7 +60,6 @@ class Restrict extends Component {
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -78,7 +68,7 @@ class Restrict extends Component {
d: 'M 124 8 L 8 124',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4,
delay: TIMES.TARGET_DURATION_MS * 0.4,
duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutSine',
}));
@ -88,7 +78,7 @@ class Restrict extends Component {
d: 'M 124 124 L 8 8',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4,
delay: TIMES.TARGET_DURATION_MS * 0.4,
duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutSine',
}));
@ -102,8 +92,7 @@ class Restrict extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Restrict);
module.exports = Restrict;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Ruin extends Component {
constructor() {
super();
@ -75,7 +67,7 @@ class Ruin extends Component {
this.animations.push(anime({
targets: ['#ruin'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -86,7 +78,6 @@ class Ruin extends Component {
rotate: 180,
easing: 'linear',
loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -99,12 +90,7 @@ class Ruin extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
try {
this.props.animCb && this.props.animCb();
} catch (e) {
console.log(e);
}
}
}
module.exports = addState(Ruin);
module.exports = Ruin;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Silence extends Component {
constructor() {
super();
@ -47,7 +39,7 @@ class Silence extends Component {
this.animations.push(anime({
targets: ['#silence'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -58,12 +50,11 @@ class Silence extends Component {
rotate: [90, 90],
scale: {
value: 1,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeInExpo',
},
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.1,
delay: TIMES.TARGET_DURATION_MS * 0.1,
duration: TIMES.TARGET_DURATION_MS * 0.2,
easing: 'easeOutSine',
}));
@ -73,7 +64,7 @@ class Silence extends Component {
d: 'M 124 8 L 8 124',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4,
delay: TIMES.TARGET_DURATION_MS * 0.4,
duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutSine',
}));
@ -83,7 +74,7 @@ class Silence extends Component {
d: 'M 124 124 L 8 8',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4,
delay: TIMES.TARGET_DURATION_MS * 0.4,
duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutSine',
}));
@ -97,8 +88,7 @@ class Silence extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Silence);
module.exports = Silence;

View File

@ -1,19 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const duration = TIMES.TARGET_DURATION_MS;
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Siphon extends Component {
constructor() {
super();
@ -43,7 +33,7 @@ class Siphon extends Component {
this.animations.push(anime({
targets: '.skill-anim',
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.3 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.3 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.POST_SKILL_DURATION_MS },
],
}));
@ -51,8 +41,7 @@ class Siphon extends Component {
anime({
targets: '#siphon',
r: 0,
delay: TIMES.TARGET_DELAY_MS,
duration,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInSine',
});
}
@ -61,8 +50,7 @@ class Siphon extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Siphon);
module.exports = Siphon;

View File

@ -1,18 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
const duration = TIMES.TARGET_DURATION_MS;
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
@ -82,7 +73,7 @@ class SiphonTick extends Component {
anime({
targets: '#siphon',
r: 600,
duration: duration * 2 / 3,
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeInSine',
});
@ -92,8 +83,8 @@ class SiphonTick extends Component {
targets: proj,
cx: 150 + (Math.random() * 300 * (Math.random() < 0.5 ? -1 : 1)),
cy: 150 + (Math.random() * 300 * (Math.random() < 0.5 ? -1 : 1)),
delay: (Math.random() * duration * 1 / 2),
duration,
delay: (Math.random() * TIMES.TARGET_DURATION_MS * 1 / 2),
duration: TIMES.TARGET_DURATION_MS,
easing: 'easeOutQuad',
});
});
@ -103,8 +94,7 @@ class SiphonTick extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(SiphonTick);
module.exports = SiphonTick;

View File

@ -1,42 +1,17 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const times = require('lodash/times');
const { TIMES } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
cx={x}
cy={y}
stroke="none"
r={radius}
fill={colour}
/>
);
}
function sword(colour) {
return (
<polygon points='150,150 100,75, 150,300, 200,75' stroke="none" fill={colour} id="sword" filter="url(#slayFilter)"></polygon>
);
}
const GREEN = '#1FF01F';
const RED = '#a52a2a';
class Slay extends Component {
constructor() {
super();
this.animations = [];
this.colour = '#a52a2a';
const points = new Array(30).fill(0);
this.charges = points.map(() => projectile(150, 420, 7, '#1FF01F'));
}
render() {
@ -47,13 +22,16 @@ class Slay extends Component {
id="slay"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 300 300">
<filter id="slayFilter">
<feGaussianBlur stdDeviation="4"/>
<feTurbulence type="turbulence" baseFrequency="0.001" numOctaves="3" result="turbulence"/>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="1" xChannelSelector="A" yChannelSelector="A"/>
</filter>
{sword(this.colour)}
{this.charges}
{times(10, () => (
<ellipse
cx={anime.random(100, 200)}
cy={anime.random(-60, -30)}
stroke="none"
rx={anime.random(5, 10)}
ry={10}
fill={RED}
/>
))}
</svg>
);
}
@ -73,73 +51,33 @@ class Slay extends Component {
anime.set('#slay', {
rotate,
});
anime.set('#slay', {
translateY: -1 * (window.innerHeight) * 0.35,
translateX: 0,
});
anime.set('#slayFilter feDisplacementMap', {
scale: 0,
});
anime.set('#sword', {
fill: this.colour,
opacity: 1,
});
this.animations.push(anime({
targets: '#slay',
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{
value: 0,
delay: TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS * 0.2,
duration: TIMES.POST_SKILL_DURATION_MS * 0.3,
}],
translateY: 0,
translateX: 0,
loop: false,
delay: TIMES.TARGET_DELAY_MS,
duration: (TIMES.TARGET_DURATION_MS * 0.5),
easing: 'easeInQuad',
}));
anime.set('#slay ellipse',{
fill: RED,
})
this.animations.push(anime({
targets: ['#slayFilter feTurbulence', '#slayFilter feDisplacementMap'],
baseFrequency: 10,
scale: 100,
delay: (TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.5),
duration: (TIMES.TARGET_DURATION_MS * 0.5),
easing: 'easeInQuad',
targets: ['#slay ellipse'],
cx: 150,
cy: 325,
duration: TIMES.TARGET_DURATION_MS * 0.2,
duration: TIMES.TARGET_DURATION_MS * 0.4,
easing: 'easeOutQuad',
direction: 'alternate',
}));
this.animations.push(anime({
targets: '#sword',
opacity: 0,
delay: (TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS),
}));
const projectiles = document.querySelectorAll('#slay circle');
projectiles.forEach(proj => {
this.animations.push(anime({
targets: proj,
cx: Math.random() * 250 + 25,
cy: Math.random() * 200 - 100,
delay: (TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS * 0.2),
duration: (TIMES.POST_SKILL_DURATION_MS * 0.3),
easing: 'easeInQuad',
}));
});
setTimeout(() => anime.set('#slay ellipse',{
fill: GREEN,
}), TIMES.TARGET_DURATION_MS * 0.5);
}
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Slay);
module.exports = Slay;

View File

@ -1,7 +1,6 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const { randomPoints } = require('../../utils');
@ -9,13 +8,6 @@ const { randomPoints } = require('../../utils');
// logarithmic spiral lifted from
// https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
@ -81,7 +73,7 @@ class Sleep extends Component {
this.animations.push(anime({
targets: ['#sleep'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
}));
@ -91,32 +83,21 @@ class Sleep extends Component {
rotate: 180,
easing: 'linear',
loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
this.animations.push(anime({
targets: ['#stun'],
opacity: [
{ value: 0, delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.8, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.8, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
}));
/* this.animations.push(anime({
targets: ['#heal'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.7, duration: TIMES.TARGET_DURATION_MS * 0.3 },
{ value: 0, delay: TIMES.POST_SKILL_DURATION_MS * 0.6, duration: TIMES.POST_SKILL_DURATION_MS * 0.4 },
],
easing: 'easeInOutSine',
}));
*/
this.animations.push(anime({
targets: ['#charges'],
opacity: 1,
delay: anime.stagger(TIMES.TARGET_DURATION_MS * 0.015, { start: TIMES.TARGET_DELAY_MS }),
delay: anime.stagger(TIMES.TARGET_DURATION_MS * 0.01),
easing: 'easeInOutSine',
}));
@ -125,11 +106,10 @@ class Sleep extends Component {
targets: ['#charges'],
cx: 0,
cy: 0,
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS * 0.75,
delay: TIMES.TARGET_DURATION_MS + TIMES.POST_SKILL_DURATION_MS * 0.75,
duration: TIMES.POST_SKILL_DURATION_MS * 0.25,
easing: 'easeInOutSine',
}));
}
// this is necessary because
@ -140,8 +120,7 @@ class Sleep extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Sleep);
module.exports = Sleep;

View File

@ -1,31 +1,16 @@
const preact = require('preact');
const { Component } = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Strike extends Component {
constructor(props) {
constructor() {
super();
this.props = props;
this.animations = [];
}
render() {
// const { x, y } = (this.props && this.props.direction) || { x: 0, y: 0 };
// const angle = (Math.atan(y / x) * (180 / Math.PI)) + 90;
// console.log(x, -y);
// console.log(angle);
// can't get this shit to work
return (
<svg
class="strike-anim"
@ -54,8 +39,8 @@ class Strike extends Component {
x: [200, 0, 200],
height: [200, 10, 0],
width: [20, 400, 0],
delay: TIMES.TARGET_DELAY_MS * 0.5,
duration: TIMES.TARGET_DURATION_MS,
delay: TIMES.TARGET_DURATION_MS * 0.2,
}));
this.animations.push(anime({
@ -64,7 +49,7 @@ class Strike extends Component {
scale: 50,
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS + (TIMES.TARGET_DURATION_MS / 3),
delay: TIMES.TARGET_DURATION_MS / 3,
duration: TIMES.TARGET_DURATION_MS / 2,
}));
}
@ -77,9 +62,7 @@ class Strike extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Strike);
module.exports = Strike;

View File

@ -1,20 +1,11 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES } = require('../../constants');
// logarithmic spiral lifted from
// https://upload.wikimedia.org/wikipedia/commons/5/5b/Logarithmic_spiral_(1).svg
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Stun extends Component {
constructor() {
super();
@ -54,7 +45,7 @@ class Stun extends Component {
this.animations.push(anime({
targets: ['#stun'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -65,7 +56,6 @@ class Stun extends Component {
rotate: 180,
easing: 'linear',
loop: true,
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
}
@ -78,8 +68,7 @@ class Stun extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Stun);
module.exports = Stun;

View File

@ -1,17 +1,9 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class Sustain extends Component {
constructor() {
super();
@ -33,17 +25,17 @@ class Sustain extends Component {
</filter>
<polyline
id="stageOne"
points='0 0'
points='128,168 80,240 176,240 128,168'
style={{ filter: 'url("#sustainFilter")' }}
/>
<polyline
id="stageTwo"
points='0 0'
points='176,240 212,216 128,96 44,216 80,240'
style={{ filter: 'url("#sustainFilter")' }}
/>
<polyline
id="stageThree"
points='0 0'
points='212,216 248,192 128,24 8,192 44,216'
style={{ filter: 'url("#sustainFilter")' }}
/>
</svg>
@ -54,7 +46,7 @@ class Sustain extends Component {
this.animations.push(anime({
targets: ['#sustain'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.8, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -62,17 +54,15 @@ class Sustain extends Component {
this.animations.push(anime({
targets: ['#stageOne'],
points: '128,168 80,240 176,240 128,168',
keyframes: [
{
stroke: [COLOURS.GREEN, COLOURS.RED],
fill: [null, COLOURS.RED],
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.2,
duration: TIMES.TARGET_DURATION_MS * 0.6,
delay: TIMES.TARGET_DURATION_MS * 0.2,
duration: TIMES.TARGET_DURATION_MS * 0.15,
easing: 'easeInOutSine',
},
],
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -82,13 +72,11 @@ class Sustain extends Component {
{
stroke: [COLOURS.GREEN, COLOURS.RED],
fill: [null, COLOURS.RED],
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.5,
duration: TIMES.TARGET_DURATION_MS * 0.6,
delay: TIMES.TARGET_DURATION_MS * 0.35,
duration: TIMES.TARGET_DURATION_MS * 0.15,
easing: 'easeInOutSine',
},
],
points: '176,240 212,216 128,96 44,216 80,240',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -98,13 +86,11 @@ class Sustain extends Component {
{
stroke: [COLOURS.GREEN, COLOURS.RED],
fill: [null, COLOURS.RED],
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.8,
duration: TIMES.TARGET_DURATION_MS * 0.4,
delay: TIMES.TARGET_DURATION_MS * 0.5,
duration: TIMES.TARGET_DURATION_MS * 0.15,
easing: 'easeInOutSine',
},
],
points: '212,216 248,192 128,24 8,192 44,216',
delay: TIMES.TARGET_DELAY_MS,
duration: TIMES.TARGET_DURATION_MS,
}));
@ -114,7 +100,7 @@ class Sustain extends Component {
scale: 10,
numOctaves: 5,
easing: 'easeOutSine',
delay: TIMES.TARGET_DELAY_MS + TIMES.TARGET_DURATION_MS * 0.4,
delay: TIMES.TARGET_DURATION_MS * 0.55,
duration: TIMES.TARGET_DURATION_MS * 0.3 + TIMES.POST_SKILL_DURATION_MS,
}));
}
@ -127,8 +113,7 @@ class Sustain extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Sustain);
module.exports = Sustain;

View File

@ -6,13 +6,6 @@ const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const { randomPoints } = require('../../utils');
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
function projectile(x, y, radius, colour) {
return (
<circle
@ -60,7 +53,7 @@ class Triage extends Component {
this.animations.push(anime({
targets: ['#triage'],
opacity: [
{ value: 1, delay: TIMES.TARGET_DELAY_MS, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 1, duration: TIMES.TARGET_DURATION_MS * 0.2 },
{ value: 0, delay: TIMES.TARGET_DURATION_MS * 0.6, duration: TIMES.TARGET_DURATION_MS * 0.2 },
],
easing: 'easeInOutSine',
@ -80,8 +73,7 @@ class Triage extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(Triage);
module.exports = Triage;

View File

@ -1,7 +1,6 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
const { connect } = require('preact-redux');
const { TIMES, COLOURS } = require('../../constants');
const { randomPoints } = require('../../utils');
@ -18,17 +17,9 @@ function projectile(x, y, radius, colour) {
);
}
const addState = connect(
function receiveState(state) {
const { animCb } = state;
return { animCb };
}
);
class TriageTick extends Component {
constructor(props) {
super();
this.team = props.team;
this.animations = [];
const points = randomPoints(15, 10, { x: 0, y: 0, width: 300, height: 400 });
this.charges = points.map(coord => projectile(coord[0], coord[1], 15, COLOURS.GREEN));
@ -79,8 +70,7 @@ class TriageTick extends Component {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
this.props.animCb && this.props.animCb();
}
}
module.exports = addState(TriageTick);
module.exports = TriageTick;

View File

@ -13,8 +13,8 @@ const { ConstructAnimation } = require('./animations');
const addState = connect(
function receiveState(state) {
const { animSource, animTarget, animText } = state;
return { animSource, animTarget, animText };
const { animating, animSource, animTarget, resolution, account } = state;
return { animating, animSource, animTarget, resolution, account };
}
);
@ -43,6 +43,7 @@ class ConstructAvatar extends Component {
}
onClick() {
if (this.props.animating) return false;
return this.animations.push(wiggle(this.props.construct.id, this.idle));
}
@ -62,19 +63,24 @@ class ConstructAvatar extends Component {
}
componentDidUpdate(prevProps) {
const { animSource, animTarget, animText, construct } = this.props;
const { animSource, animTarget, resolution, construct, account } = this.props;
// a different text object and text construct
if (animText && animText !== prevProps.animText && animText.constructId === construct.id) {
return wiggle(construct.id, this.idle);
if (resolution && resolution !== prevProps.resolution && resolution.event[1].construct === construct.id) {
const type = resolution.event[0];
// only trigger the wiggle on damage and ko events rather than spam it on everything
// also stops wiggle triggering when invert effect is applied
if (['Damage', 'Ko'].includes(type)) return wiggle(construct.id, this.idle);
}
// different source object and source construct
if (animSource && animSource !== prevProps.animSource && animSource.constructId === construct.id) {
return sourceCast(animSource.constructId, animSource.direction, this.idle);
if (animSource && animSource !== prevProps.animSource && animSource.event[1].construct === construct.id) {
const { construct: constructId, player, direction: [x, y] } = animSource.event[1];
const animY = y && player === account.id ? -1 : y;
return sourceCast(constructId, { x, y: animY }, this.idle);
}
// different target object and target construct
if (animTarget && animTarget !== prevProps.animTarget && animTarget.constructId.includes(construct.id)) {
if (animTarget && animTarget !== prevProps.animTarget && animTarget.event[1].construct.includes(construct.id)) {
switch (animTarget.skill) {
case 'Banish': return banish(construct.id, this.idle);
case 'Invert': return invert(construct.id, this.idle);
@ -86,26 +92,23 @@ class ConstructAvatar extends Component {
shouldComponentUpdate(newProps) {
const { animSource, animTarget, animText, construct, mouseOver } = newProps;
const { animSource, animTarget, resolution, construct, mouseOver } = newProps;
if (animSource !== this.props.animSource) return true;
if (animTarget !== this.props.animTarget) return true;
if (animText !== this.props.animText) return true;
if (resolution !== this.props.resolution) return true;
if (construct !== this.props.construct) return true;
if (mouseOver !== this.props.mouseOver) return true;
return false;
}
}
function ConstructText(props) {
function ConstructName(props) {
const { construct } = props;
if (!construct) return false;
const text = construct.name;
return <h3 class={'name'}><span>{text}</span></h3>;
return <h3 class={'name'}><span>{construct.name}</span></h3>;
}
module.exports = {
ConstructAvatar: addState(ConstructAvatar),
ConstructText,
ConstructName,
};

View File

@ -10,6 +10,7 @@ const addState = connect(
function receiveState(state) {
const {
ws,
authenticated,
account,
game,
instance,
@ -17,6 +18,7 @@ const addState = connect(
} = state;
return {
authenticated,
account,
game,
instance,
@ -28,6 +30,7 @@ const addState = connect(
function Controls(args) {
const {
game,
authenticated,
account,
instance,
nav,
@ -38,6 +41,7 @@ function Controls(args) {
if (game) return <GameCtrl />;
if (instance) return <InstanceCtrl />;
if (!authenticated) return false;
if (nav === 'play' || nav === 'shop' || nav === 'reshape' || !nav) return <PlayCtrl />
if (nav === 'team' || nav === 'account') return <TeamCtrl />

View File

@ -1,190 +0,0 @@
const { connect } = require('preact-redux');
const preact = require('preact');
// const actions = require('../actions');
const shapes = require('./shapes');
const { ConstructAvatar } = require('./construct');
// const { ConstructAnimation } = require('./animations');
const addState = connect(
function receiveState(state) {
const {
account,
itemInfo,
demo,
} = state;
return {
account,
itemInfo,
demo,
};
}
/* function receiveDispatch(dispatch) {
function setAnimTarget(anim) {
dispatch(actions.setAnimTarget(anim));
}
return { setAnimTarget };
} */
);
function Demo(args) {
const {
demo,
itemInfo,
account,
// setAnimTarget,
} = args;
if (!demo || !itemInfo.items.length || account) return false;
const { combiner, items, equipping, equipped, players, combo } = demo;
const vboxDemo = () => {
function stashBtn(i, j) {
if (!i) return <button disabled class='empty' >&nbsp;</button>;
const highlighted = combiner.indexOf(j) > -1;
const classes = `${highlighted ? 'highlight' : ''}`;
if (shapes[i]) {
return <button class={classes} key={j}>{shapes[i]()}</button>;
}
return <button class={classes}>{i}</button>;
}
function combinerBtn() {
let text = '';
if (combiner.length < 3) {
for (let i = 0; i < 3; i++) {
if (combiner.length > i) {
text += '■ ';
} else {
text += '▫ ';
}
}
} else {
text = 'combine';
}
return (
<button
class='vbox-btn'
disabled={combiner.length !== 3}>
{text}
</button>
);
}
function stashElement() {
return (
<div class="vbox">
<div class='vbox-section'>
<h2 class='colour-info'>
VBOX PHASE {shapes.Red()} {shapes.Green()} {shapes.Blue()}
</h2>
<p>
Combine colours with base skills and specialisations to build an array of powerful variants.
</p>
</div>
<div>&nbsp;</div>
<div class='vbox-section'>
<div class='vbox-items'>
{items.map((i, j) => stashBtn(i, j))}
</div>
{combinerBtn()}
</div>
</div>
);
}
return (
<div class="news vbox-demo">
{stashElement()}
</div>
);
};
const vboxConstructs = () => {
const btnClass = equipping
? 'equipping empty gray'
: 'empty gray';
const constructEl = c => (
<div class="instance-construct">
<h2 class="name" >{c.name}</h2>
<ConstructAvatar construct={c} />
<div class="skills">
{equipped
? <button>{combo}</button>
: <button disabled={!equipping} class={btnClass}>SKILL</button>
}
<button disabled={!equipping} class={btnClass}>SKILL</button>
<button disabled={!equipping} class={btnClass}>SKILL</button>
</div>
<div class="specs">
</div>
<div class="stats">
</div>
</div>
);
return (
<section class="construct-section">
<div>
<h2>CONSTRUCTS</h2>
<p><b>Constructs</b> are the units you control. They are reset every game and their initial appearance is randomly generated.</p>
<p><b>Skills</b> and <b>Specs</b> you create in the <b>VBOX Phase</b> are equipped to your constructs to create a build.</p>
</div>
<div class='construct-list'>
{constructEl(players[0].constructs[0])}
</div>
</section>
);
};
const gameDemo = () => {
return (
<section class="game-demo">
<div>
<h2>COMBAT PHASE</h2>
<p>Battle your opponent using dynamic team builds from the VBOX phase.</p>
<p>The skills crafted can be used to damage the opponent or support your team.</p>
<p>Simultaneous turn based combat: each team picks targets for their skills during this phase.</p>
<p>The damage dealt by skills, cast order and construct life depend on your decisions in the VBOX phase.</p>
</div>
<div class="game">
<div class="game-construct">
<div class="left"></div>
<div class="right">
<ConstructAvatar construct={players[1].constructs[0]} />
</div>
</div>
<div></div>
<div class="game-construct">
<div class="left"></div>
<div class="right">
<ConstructAvatar construct={players[1].constructs[1]} />
</div>
</div>
</div>
</section>
);
};
return (
<section class='demo news top'>
{gameDemo()}
{vboxDemo()}
{vboxConstructs()}
</section>
);
}
module.exports = addState(Demo);

View File

@ -0,0 +1,85 @@
// const { connect } = require('preact-redux');
const preact = require('preact');
const { connect } = require('preact-redux');
const { errorToast, infoToast } = require('../utils');
const actions = require('./../actions');
const VERSION = process.env.npm_package_version;
const Welcome = require('./welcome');
const addState = connect(
function receiveState(state) {
const {
ws,
account,
} = state;
function sendInstancePractice() {
ws.sendInstancePractice();
}
return {
account,
sendInstancePractice,
};
},
);
function Play(args) {
const {
account,
sendInstancePractice,
} = args;
const news = (
<div class="list">
<div class="intro">
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
<p>
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
Outplay your opponent across multiple rounds by adapting to an always shifting meta. <br />
</p>
</div>
<div class="awards"></div>
</div>
);
const list = () => {
return (
<div class='list play'>
<figure>
<button
class="ready"
onClick={() => sendInstancePractice()}>
Play
</button>
<figcaption>Learn MNML</figcaption>
</figure>
<figure>
<button
class='discord-btn'
onClick={() => window.open('https://discord.gg/YJJgurM') }>
&nbsp;
</button>
<figcaption>Join the Community</figcaption>
</figure>
</div>
);
};
return (
<main>
<div class="logo"/>
<hr />
{list()}
<hr />
<Welcome />
<hr />
{news}
</main>
);
}
module.exports = addState(Play);

View File

@ -0,0 +1,89 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const reactStringReplace = require('react-string-replace');
const shapes = require('./shapes');
const { removeTier } = require('../utils');
const { TIMES } = require('./../constants');
const addState = connect(({ resolution, itemInfo }) => ({ resolution, itemInfo }));
class AnimText extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.resolution !== this.props.resolution) return true;
return false;
}
componentDidUpdate(prevProps) {
const { resolution, construct } = this.props;
if (resolution && resolution !== prevProps.resolution && resolution.event[1].construct === construct.id) {
anime({
targets: '.combat-text',
top: '40%',
duration: TIMES.POST_SKILL_DURATION_MS - 500,
easing: 'easeOutQuad',
});
}
}
render() {
const { construct, resolution, itemInfo } = this.props;
if (resolution && resolution.event[1].construct === construct.id) {
const itemSourceDescription = () => {
const itemSource = itemInfo.combos.filter(c => c.item === removeTier(resolution.skill));
const itemSourceInfo = itemSource.length
? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}`
: false;
const itemRegEx = /(Red|Blue|Green)/;
return reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
};
const generateAnimText = () => {
const [type, event] = resolution.event;
switch (type) {
case 'Damage': {
const { amount, mitigation, colour } = event;
const mitigationText = mitigation ? `(${mitigation})` : '';
return <h1><span class={colour.toLowerCase()}>-{amount} {mitigationText} </span></h1>;
}
case 'Healing': {
const { amount, overhealing, colour } = event;
const overHealingText = overhealing ? `(${overhealing} OH)` : '';
return <h1><span class={colour.toLowerCase()}>+{amount} {overHealingText}</span></h1>;
}
case 'Effect': {
const { effect, duration } = event;
return <h1><span>+{effect} {duration}T</span></h1>;
}
case 'Removal': {
const { effect } = event;
if (!effect) return <h1><span>Effect Removal</span></h1>;
return <h1><span>-{effect}</span></h1>;
}
case 'Ko': return <h1><span>KO!</span></h1>;
case 'Reflection': return <h1><span>REFLECT</span></h1>;
default: return false;
}
};
// We don't send inversion / disable / immune event text
/* case 'Inversion': return <h1><span>INVERT</span></h1>;
case 'Disable': {
const { disable } = event;
return <h1><span>{disable}</span></h1>;
}
case 'Immunity': return <h1><span>IMMUNE</span></h1>; */
return (
<div class="combat-text">
<h2><span>{resolution.skill}</span></h2>
<span>{itemSourceDescription()}</span>
{generateAnimText()}
</div>
);
}
return null;
}
}
module.exports = addState(AnimText);

View File

@ -0,0 +1,73 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const reactStringReplace = require('react-string-replace');
const actions = require('../actions');
const shapes = require('./shapes');
const { INFO } = require('./../constants');
const addState = connect(
({ resolution, itemInfo, gameSkillInfo }) => ({ resolution, itemInfo, gameSkillInfo }),
function receiveDispatch(dispatch) {
function setGameEffectInfo(info) {
dispatch(actions.setGameEffectInfo(info));
}
return { setGameEffectInfo };
}
);
class GameConstructEffects extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.resolution && newProps.resolution !== this.props.resolution) {
const [type, info] = newProps.resolution.event;
if (info.construct === this.props.construct.id
&& (type === 'Effect' || type === 'Removal')) return true;
}
if (newProps.construct !== this.props.construct) return true;
return false;
}
render() {
const {
resolution,
construct,
gameSkillInfo,
setGameEffectInfo,
itemInfo,
} = this.props;
function hoverInfo(e, info) {
e.stopPropagation();
return setGameEffectInfo(info);
}
if (gameSkillInfo && gameSkillInfo.constructId === construct.id) {
const fullInfo = itemInfo.items.find(k => k.item === gameSkillInfo.skill) || INFO[gameSkillInfo.skill];
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, match => shapes[match]());
const speed = <span> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </span>;
return (
<div class="skill-description">
<h2><span> {gameSkillInfo.skill} </span></h2>
<span>{infoDescription} </span> <br />
{speed}
</div>);
}
const effects = resolution ? resolution.event[1].display.effects : construct.effects;
const renderEffects = effects.length
? effects.map(c =>
<div key={c.effect}>
<span key={c.effect} onMouseOver={e => hoverInfo(e, c)}
onMouseOut={e => hoverInfo(e, null)}> {c.effect} - {c.duration}T
</span>
</div>)
: null;
return (<div class="effects"> {renderEffects} </div>);
}
}
module.exports = addState(GameConstructEffects);

View File

@ -1,95 +1,23 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const anime = require('animejs').default;
const range = require('lodash/range');
const reactStringReplace = require('react-string-replace');
const { STATS, removeTier } = require('../utils');
const { ConstructAvatar, ConstructText } = require('./construct');
const shapes = require('./shapes');
const { INFO, TIMES } = require('./../constants');
const actions = require('../actions');
const { ConstructAvatar, ConstructName } = require('./construct');
const SkillBtn = require('./skill.btn');
const addStateText = connect(({ animText, itemInfo }) => ({ animText, itemInfo }));
class combatText extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.animText !== this.props.animText) return true;
return false;
}
componentDidUpdate(prevProps) {
const { animText, construct } = this.props;
if (animText && animText !== prevProps.animText && animText.constructId === construct.id) {
anime({
targets: '.combat-text',
top: '40%',
duration: TIMES.POST_SKILL_DURATION_MS - 500,
easing: 'easeOutQuad',
});
}
}
render(props) {
const { construct, animText, itemInfo } = props;
if (animText && animText.constructId === construct.id) {
const itemSourceDescription = () => {
const itemSource = itemInfo.combos.filter(c => c.item === removeTier(animText.skill));
const itemSourceInfo = itemSource.length
? `${itemSource[0].components[0]} ${itemSource[0].components[1]} ${itemSource[0].components[2]}`
: false;
const itemRegEx = /(Red|Blue|Green)/;
return reactStringReplace(itemSourceInfo, itemRegEx, match => shapes[match]());
};
const animationTextHtml = () => {
// monkaW hack to make red / blue recharge work nicely
if (animText.css === 'rb') {
const text = animText.text.split(' ');
return (
<h1>
<span class="red">{text[0]}</span>&nbsp;
<span class="blue">{text[1]}</span>
</h1>
);
}
return (
<h1 class={animText.css}>
<span>{animText.text}</span>
</h1>
);
};
return (
<div class="combat-text">
<h2><span>{animText.skill}</span></h2>
<span>{itemSourceDescription()}</span>
{animationTextHtml()}
</div>
);
}
return null;
}
}
const ConstructAnimationText = addStateText(combatText);
const SkillBtn = require('./game.construct.skill.btn');
const ConstructAnimationText = require('./game.construct.anim.text');
const ConstructLife = require('./game.construct.life');
const ConstructEffectBox = require('./game.construct.effect.box');
const addState = connect(
function receiveState(state) {
const {
ws,
game,
account,
activeSkill,
animFocus,
animating,
animText,
gameSkillInfo,
itemInfo,
tutorialGame,
resolution,
} = state;
function selectSkillTarget(targetConstructId) {
@ -100,65 +28,23 @@ const addState = connect(
}
return {
game,
account,
animating,
animFocus,
animText,
activeSkill,
animFocus,
resolution,
selectSkillTarget,
gameSkillInfo,
itemInfo,
tutorialGame,
};
},
function receiveDispatch(dispatch) {
function setGameEffectInfo(info) {
dispatch(actions.setGameEffectInfo(info));
}
function setTutorialGameClear(activeSkill, tutorialGame) {
if (activeSkill && tutorialGame) dispatch(actions.setTutorialGame(null));
}
return { setGameEffectInfo, setTutorialGameClear };
}
);
const eventClasses = (animating, animFocus, construct, postSkill) => {
if (!animating) return '';
if (!postSkill) {
if (animFocus.includes(construct.id)) return '';
return 'unfocus';
}
if (postSkill.constructId !== construct.id) {
if (animFocus.includes(construct.id)) return '';
return 'unfocus';
}
if (postSkill.effects) construct.effects = postSkill.effects;
construct.green_life.value = postSkill.life.green;
construct.red_life.value = postSkill.life.red;
construct.blue_life.value = postSkill.life.blue;
return postSkill.css;
};
class GameConstruct extends preact.Component {
constructor() {
super();
this.resolvedLength = 0;
}
shouldComponentUpdate(newProps) {
if (newProps.activeSkill !== this.props.activeSkill) return true;
if (newProps.animFocus !== this.props.animFocus) return true;
if (newProps.animText !== this.props.animText) return true;
if (newProps.animating !== this.props.animating) return true;
if (newProps.construct !== this.props.construct) return true;
if (newProps.player !== this.props.player) return true;
if (newProps.tutorialGame !== this.props.tutorialGame) return true;
if (newProps.gameSkillInfo !== this.props.gameSkillInfo) return true;
if (newProps.resolution && newProps.resolution !== this.props.resolution) {
const [type, variant] = newProps.resolution.event;
if (variant.construct === this.props.construct.id && type === 'Ko') return true;
}
return false;
}
@ -167,84 +53,46 @@ class GameConstruct extends preact.Component {
// Changing state variables
activeSkill,
animFocus,
animText,
animating,
resolution,
selectSkillTarget,
construct,
player,
tutorialGame,
gameSkillInfo,
// Constants
i,
itemInfo,
// Functions
selectSkillTarget,
setTutorialGameClear,
setGameEffectInfo,
} = this.props;
const koEvent = animText ? animText.text === 'KO!' && animText.constructId === construct.id : false;
const ko = construct.green_life.value === 0 && !koEvent ? 'ko' : '';
const classes = eventClasses(animating, animFocus, construct, animText);
const cssClass = ['ko-transition', 'unfocus'].includes(classes) ? classes : null;
const stats = ['RedLife', 'GreenLife', 'BlueLife'].map((s, j) => (
<div key={j} alt={STATS[s].stat}>
{shapes[s]()}
<div class="max" >{construct[STATS[s].stat].value} / {construct[STATS[s].stat].max}</div>
<div class="value" >{construct[STATS[s].stat].value}</div>
</div>
));
const skills = range(0, 3)
.map(j => <SkillBtn key={j} construct={construct} i={j} j={i} animating={animating} />);
let crypSkills = <div></div>;
if (player) crypSkills = (<div class="skills"> {skills} </div>);
function hoverInfo(e, info) {
e.stopPropagation();
return setGameEffectInfo(info);
}
const effectBox = () => {
if (gameSkillInfo && gameSkillInfo.constructId === construct.id) {
const fullInfo = itemInfo.items.find(k => k.item === gameSkillInfo.skill) || INFO[gameSkillInfo.skill];
const regEx = /(RedPower|BluePower|GreenPower|RedLife|BlueLife|GreenLife|SpeedStat)/;
const infoDescription = reactStringReplace(fullInfo.description, regEx, match => shapes[match]());
const speed = <span> Speed {shapes.SpeedStat()} multiplier {fullInfo.speed * 4}% </span>;
return (
<div class="skill-description">
<h2><span> {gameSkillInfo.skill} </span></h2>
<span>{infoDescription} </span> <br />
{speed}
</div>);
// construct green_life comes from game state and won't update during animations
// treat the construct as ko for the remainder of the anims if ko event occurs
const ko = construct.green_life.value === 0 || this.ko ? 'ko' : '';
const koEvent = () => {
if (resolution) {
const [type, variant] = resolution.event;
if (variant.construct === construct.id && type === 'Ko') {
this.ko = true;
return 'ko-transition';
}
}
const effects = construct.effects.length
? construct.effects.map(c =>
<div key={c.effect}>
<span key={c.effect} onMouseOver={e => hoverInfo(e, c)}
onMouseOut={e => hoverInfo(e, null)}> {c.effect} - {c.duration}T
</span>
</div>)
: null;
return (<div class="effects"> {effects} </div>);
return '';
};
const unfocus = animFocus && !animFocus.includes(construct.id) ? 'unfocus' : '';
const crypSkills = player
? <div class="skills"> {range(0, 3).map(j => <SkillBtn key={j} construct={construct} i={j} />)} </div>
: <div></div>;
return (
<div
onClick={() => {
selectSkillTarget(construct.id);
setTutorialGameClear(activeSkill, tutorialGame);
}}
onClick={() => selectSkillTarget(construct.id)}
style={ activeSkill ? { cursor: 'pointer' } : {}}
class={`game-construct ${ko} ${cssClass}`}>
class={`game-construct ${ko} ${koEvent()} ${unfocus}`}>
<div class="left">
{crypSkills}
{effectBox()}
<ConstructEffectBox construct={construct} />
</div>
<div class="right">
<div class="stats"> {stats} </div>
<ConstructLife construct={construct} />
<ConstructAvatar construct={construct} />
<ConstructText construct={construct} />
<ConstructName construct={construct} />
<ConstructAnimationText construct={construct} />
</div>
</div>

View File

@ -0,0 +1,49 @@
const preact = require('preact');
const { connect } = require('preact-redux');
const shapes = require('./shapes');
const addState = connect(({ resolution }) => ({ resolution }));
class GameConstructLife extends preact.Component {
shouldComponentUpdate(newProps) {
if (newProps.resolution && newProps.resolution !== this.props.resolution) {
const [type, variant] = newProps.resolution.event;
if (variant.construct === this.props.construct.id
&& (type === 'Damage' || type === 'Healing')) return true;
}
if (newProps.construct !== this.props.construct) return true;
return false;
}
render() {
const { construct, resolution } = this.props;
const lifeBars = (redLife, greenLife, blueLife) => {
return (
<div class="stats">
<div>
{shapes.RedLife()}
<div class="max" >{redLife} / {construct.red_life.max}</div>
</div>
<div>
{shapes.GreenLife()}
<div class="max" >{greenLife} / {construct.green_life.max}</div>
</div>
<div>
{shapes.BlueLife()}
<div class="max" >{blueLife} / {construct.blue_life.max}</div>
</div>
</div>
);
};
if (resolution) {
const { red, blue, green } = resolution.event[1].display;
return lifeBars(red, green, blue);
}
return lifeBars(construct.red_life.value, construct.green_life.value, construct.blue_life.value);
}
}
module.exports = addState(GameConstructLife);

View File

@ -10,11 +10,13 @@ const addState = connect(
const {
activeSkill,
game,
animating,
} = state;
return {
activeSkill,
game,
animating,
};
},

View File

@ -8,6 +8,7 @@ const addState = connect(
const {
ws,
account,
authenticated,
nav,
} = state;
@ -22,6 +23,7 @@ const addState = connect(
return {
account,
authenticated,
nav,
sendInstanceState,
@ -48,6 +50,7 @@ const addState = connect(
function Header(args) {
const {
account,
authenticated,
nav,
sendAccountStates,
@ -56,6 +59,8 @@ function Header(args) {
if (!account) return false;
if (!authenticated) return false;
function navTo(p) {
return setNav(p);
}
@ -68,11 +73,6 @@ function Header(args) {
return (
<header>
<div class="options">
<button
onClick={() => navTo('play')}
class='logo login-btn'>
&nbsp;
</button>
<button
onClick={() => navTo('play')}
class={`login-btn ${nav === 'play' ? 'highlight' : ''}`}>

View File

@ -23,6 +23,7 @@ const addState = connect(
function Top(args) {
const {
nav,
authenticated,
} = args;
if (nav === 'account') return <AccountTop />;

View File

@ -4,27 +4,38 @@ const { connect } = require('preact-redux');
const Main = require('./main');
// const Nav = require('./nav');
const Controls = require('./controls');
const FrontPage = require('./front.page');
const Noise = require('./noise');
const addState = connect(
({ game, instance }) => ({ game, instance })
({ game, instance, authenticated }) => ({ game, instance, authenticated })
);
function Mnml(args) {
const {
game,
instance,
authenticated,
} = args;
const rotateClass = (game || instance) && window.innerHeight < 900 && window.innerWidth < window.innerHeight
? 'show'
: '';
if (!authenticated && !instance && !game) return (
<div id="mnml" class='front-page'>
<Noise />
<FrontPage />
<div id="rotate" class={rotateClass} ></div>
</div>
);
return (
<div id="mnml">
<Main />
<Controls />
<div id="rotate" class={rotateClass} >
</div>
<Noise />
<div id="rotate" class={rotateClass} ></div>
</div>
);
}

View File

@ -0,0 +1,75 @@
const preact = require('preact');
const { Component } = require('preact');
const anime = require('animejs').default;
class Noise extends Component {
constructor() {
super();
this.animations = [];
}
render() {
return (
<svg
version="1.1"
id="noise"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 400">
<filter id='noiseFilter'>
<feTurbulence type="turbulence" baseFrequency="0.2" numOctaves="2" result="turbulence"></feTurbulence>
<feDisplacementMap in2="turbulence" in="SourceGraphic" scale="2" xChannelSelector="R" yChannelSelector="G"></feDisplacementMap>
</filter>
</svg>
);
}
componentDidMount() {
this.animations.push(anime({
targets: ['#noiseFilter feTurbulence', '#noiseFilter feDisplacementMap'],
easing: 'linear',
loop: true,
keyframes: [
{
baseFrequency: 0.5,
duration: () => anime.random(1000, 2000),
},
],
}));
this.animations.push(anime({
targets: ['#noiseFilter feDisplacementMap'],
easing: 'linear',
loop: true,
keyframes: [
{
scale: 2,
duration: () => anime.random(2000, 5000),
},
{
scale: 4,
duration: () => anime.random(150, 250),
},
{
scale: 2,
duration: () => anime.random(100, 150),
},
{
scale: 4,
duration: () => anime.random(150, 250),
},
],
}));
}
// this is necessary because
// skipping / timing / unmounting race conditions
// can cause the animations to cut short, this will ensure the values are reset
// because preact will recycle all these components
componentWillUnmount() {
for (let i = this.animations.length - 1; i >= 0; i--) {
this.animations[i].reset();
}
}
}
module.exports = Noise;

View File

@ -6,11 +6,11 @@ const reactStringReplace = require('react-string-replace');
const throttle = require('lodash/throttle');
const shapes = require('./shapes');
const { effectInfo, removeTier } = require('../utils');
const { effectInfo } = require('../utils');
const addState = connect(
({ game, account, animSkill, animating, itemInfo, gameEffectInfo, tutorialGame }) =>
({ game, account, animSkill, animating, itemInfo, gameEffectInfo, tutorialGame })
({ game, account, animating, itemInfo, gameEffectInfo, tutorialGame }) =>
({ game, account, animating, itemInfo, gameEffectInfo, tutorialGame })
);
class TargetSvg extends Component {
@ -31,7 +31,6 @@ class TargetSvg extends Component {
if (newProps.game !== this.props.game) return true;
if (newProps.account !== this.props.account) return true;
if (newProps.animating !== this.props.animating) return true;
if (newProps.animSkill !== this.props.animSkill) return true;
if (newProps.gameEffectInfo !== this.props.gameEffectInfo) return true;
if (newProps.tutorialGame !== this.props.tutorialGame) return true;
if (newState.width !== this.state.width) return true;
@ -44,12 +43,9 @@ class TargetSvg extends Component {
// Changing State Variables
account,
animating,
animSkill,
game,
gameEffectInfo,
tutorialGame,
// Static
itemInfo,
} = props;
const { width, height } = state;
@ -87,14 +83,14 @@ class TargetSvg extends Component {
const otherTeam = game.players.find(t => t.id !== account.id);
const playerTeamIds = playerTeam.constructs.map(c => c.id);
const outgoing = game.stack.filter(stack => playerTeamIds.includes(stack.source_construct_id));
const outgoing = game.stack.filter(stack => stack.player === account.id);
function getPath(cast) {
const source = playerTeam.constructs.findIndex(c => c.id === cast.source_construct_id);
const defensive = playerTeamIds.includes(cast.target_construct_id);
const source = playerTeam.constructs.findIndex(c => c.id === cast.source);
const defensive = playerTeamIds.includes(cast.target);
const target = defensive
? playerTeam.constructs.findIndex(c => c.id === cast.target_construct_id)
: otherTeam.constructs.findIndex(c => c.id === cast.target_construct_id);
? playerTeam.constructs.findIndex(c => c.id === cast.target)
: otherTeam.constructs.findIndex(c => c.id === cast.target);
const skillNumber = window.innerWidth <= 800 // mobile styling trigger
? playerTeam.constructs[source].skills.findIndex(s => s.skill === cast.skill)

View File

@ -71,7 +71,6 @@ class Combos extends preact.Component {
<div class="combos">
<div class="combo-header">
<h2>COMBOS</h2>
Combine colours and items.
</div>
<div class="combo-list"
onMouseOver={e => e.stopPropagation()}

View File

@ -5,10 +5,9 @@ const Login = require('./welcome.login');
const Register = require('./welcome.register');
const Help = require('./welcome.help');
// const About = require('./welcome.about');
const Demo = require('./demo');
function Welcome() {
const page = this.state.page || 'register';
const page = this.state.page || 'login';
const pageEl = () => {
if (page === 'login') return <Login />;
@ -17,65 +16,32 @@ function Welcome() {
return false;
};
const news = (
<div class="news">
<p> Welcome to mnml.</p>
<p> MNML is a turn-based 1v1 strategy game in an abstract setting. </p>
<p>
Build a unique team of 3 constructs from a range of skills and specialisations.<br />
Outplay your opponent in multiple rounds by adapting to an always shifting meta. <br />
Simple rules, complex interactions and unique mechanics.<br />
</p>
<p> Free to play, no pay to win. Register to start playing.<br /></p>
<a href='https://www.youtube.com/watch?v=VtZLlkpJuS8'>Tutorial Playthrough on YouTube</a>
</div>
);
const main = (['login', 'register', 'help'].includes(page))
? <section>{news}{pageEl()}</section>
: <Demo />;
const form = <div>{pageEl()}</div>;
return (
<main class="menu welcome">
<header>
<div class="options">
<button
onClick={() => this.setState({ page: 'login' })}
class='logo login-btn'>
&nbsp;
</button>
<button
class={`login-btn ${page === 'login' ? 'highlight' : ''}`}
disabled={page === 'login'}
onClick={() => this.setState({ page: 'login' })}>
Login
</button>
<button
class={`login-btn ${page === 'register' ? 'highlight' : ''}`}
disabled={page === 'register'}
onClick={() => this.setState({ page: 'register' })}>
Register
</button>
<button
class={`login-btn ${page === 'info' ? 'highlight' : ''}`}
disabled={page === 'info'}
onClick={() => this.setState({ page: 'info' })}>
Info
</button>
<button
class={`login-btn ${page === 'help' ? 'highlight' : ''}`}
disabled={page === 'help'}
onClick={() => this.setState({ page: 'help' })}>
Help
</button>
</div>
</header>
<div class="top">
{main}
<header>
<div class="options">
<button
class={`login-btn ${page === 'login' ? 'highlight' : ''}`}
disabled={page === 'login'}
onClick={() => this.setState({ page: 'login' })}>
Login
</button>
<button
class={`login-btn ${page === 'register' ? 'highlight' : ''}`}
disabled={page === 'register'}
onClick={() => this.setState({ page: 'register' })}>
Register
</button>
<button
class={`login-btn ${page === 'help' ? 'highlight' : ''}`}
disabled={page === 'help'}
onClick={() => this.setState({ page: 'help' })}>
Help
</button>
</div>
</main>
{form}
</header>
);
}

View File

@ -5,8 +5,7 @@ const eachSeries = require('async/eachSeries');
const sample = require('lodash/sample');
const actions = require('./actions');
const { TIMES } = require('./constants');
const animations = require('./animations.utils');
const { setAnimations, clearAnimations } = require('./animations.utils');
const { infoToast, errorToast } = require('./utils');
const { tutorialVbox } = require('./tutorial.utils');
@ -28,10 +27,13 @@ function registerEvents(store) {
function clearTutorial() {
store.dispatch(actions.setTutorial(null));
localStorage.setItem('tutorial-complete', true);
}
function clearTutorialGame() {
store.dispatch(actions.setTutorialGame(null));
}
function setPing(ping) {
store.dispatch(actions.setPing(ping));
}
@ -73,67 +75,23 @@ function registerEvents(store) {
}
function setGame(game) {
const { game: currentGame, account, ws, animating } = store.getState();
const { game: currentGame, ws, animating } = store.getState();
if (animating) return false;
if (game && currentGame) {
if (game.resolved.length !== currentGame.resolved.length) {
if (game.resolutions.length !== currentGame.resolutions.length) {
store.dispatch(actions.setAnimating(true));
store.dispatch(actions.setGameSkillInfo(null));
// stop fetching the game state til animations are done
const newRes = game.resolved.slice(currentGame.resolved.length);
const newRes = game.resolutions[game.resolutions.length - 1];
return eachSeries(newRes, (r, cb) => {
if (!r.event) return cb();
let timeout = animations.getTime(r.stages);
const anims = animations.getObjects(r, game, account);
const text = animations.getText(r);
store.dispatch(actions.setAnimFocus(animations.getFocusTargets(r, game)));
if (anims.animSkill) store.dispatch(actions.setAnimSkill(anims.animSkill));
if (r.stages.includes('START_SKILL') && anims.animSource) {
store.dispatch(actions.setAnimSource(anims.animSource));
store.dispatch(actions.setAnimText(null));
}
if (r.stages.includes('END_SKILL') && anims.animTarget) {
store.dispatch(actions.setAnimTarget(anims.animTarget));
store.dispatch(actions.setAnimText(null));
if (animations.isCbAnim(anims.animSkill)) store.dispatch(actions.setAnimCb(cb));
}
if (r.stages.includes('POST_SKILL') && text) {
// timeout to prevent text classes from being added too soon
if (timeout === TIMES.POST_SKILL_DURATION_MS) {
store.dispatch(actions.setAnimText(text));
} else {
setTimeout(
() => store.dispatch(actions.setAnimText(text)),
timeout - TIMES.POST_SKILL_DURATION_MS - 700
);
timeout -= 700;
}
}
return setTimeout(() => {
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
// store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimFocus([]));
if (r.stages.includes('END_SKILL') && animations.isCbAnim(anims.animSkill)) return true;
return cb();
}, timeout);
if (r.delay === 0) return cb(); // TargetKo etc
setAnimations(r, store);
return setTimeout(cb, r.delay);
}, err => {
if (err) return console.error(err);
// clear animation state
store.dispatch(actions.setAnimSkill(null));
store.dispatch(actions.setAnimSource(null));
store.dispatch(actions.setAnimTarget(null));
store.dispatch(actions.setAnimText(null));
store.dispatch(actions.setAnimating(false));
store.dispatch(actions.setGameEffectInfo(null));
clearAnimations(store);
// set the game state so resolutions don't fire twice
store.dispatch(actions.setGame(game));
ws.sendGameState(game.id);
@ -146,16 +104,21 @@ function registerEvents(store) {
}
function setAccount(account) {
if (account && process.env.NODE_ENV !== 'development') {
LogRocket.init('yh0dy3/mnml');
LogRocket.identify(account.id, account);
store.dispatch(actions.setAccount(account));
}
if (window.Notification) {
window.Notification.requestPermission();
}
function setAuthenticated(account) {
if (account && window.Notification) {
window.Notification.requestPermission();
}
if (process.env.NODE_ENV !== 'development') {
LogRocket.identify(account.id, account);
}
store.dispatch(actions.setAccount(account));
store.dispatch(actions.setTutorial(null));
store.dispatch(actions.setAuthenticated(true));
}
function setEmail(email) {
@ -220,23 +183,14 @@ function registerEvents(store) {
const player = v.players.find(p => p.id === account.id);
store.dispatch(actions.setPlayer(player));
const skip = v.time_control === 'Practice' && v.phase === 'Lobby';
if (skip) {
ws.sendInstanceReady(v.id);
}
if (tutorial) tutorialVbox(player, store, tutorial);
if (v.phase === 'Finished') {
ws.sendAccountInstances();
}
// instance.mobile.less hides info at @media 1000
if (localStorage.getItem('tutorial-complete') || window.innerWidth <= 1100) {
store.dispatch(actions.setTutorial(null));
} else if (v.time_control === 'Practice' && v.rounds.length === 1 && tutorial) {
tutorialVbox(player, store, tutorial);
}
}
return store.dispatch(actions.setInstance(v));
}
@ -252,94 +206,6 @@ function registerEvents(store) {
return store.dispatch(actions.setItemInfo(v));
}
function setDemo(d) {
const vboxDemo = {
players: d,
combiner: [],
equipped: false,
equipping: false,
};
const startDemo = () => {
const { account, itemInfo } = store.getState();
if (account) return false;
if (!itemInfo || itemInfo.items.length === 0) return setTimeout(startDemo, 500);
store.dispatch(actions.setAnimTarget(null));
const bases = ['Attack', 'Stun', 'Buff', 'Debuff', 'Block'];
const combo = sample(itemInfo.combos.filter(i => bases.some(b => i.components.includes(b))));
vboxDemo.combo = combo.item;
vboxDemo.items = combo.components;
store.dispatch(actions.setDemo(vboxDemo));
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0] }))), 500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0, 1] }))), 1000);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0, 1, 2] }))), 1500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [], items: [vboxDemo.combo, '', ''] }))), 2500);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [0], items: [vboxDemo.combo, '', ''], equipping: true }))), 3000);
setTimeout(() => store.dispatch(actions.setDemo(Object.assign({}, vboxDemo, { combiner: [], items: ['', '', ''], equipped: true, equipping: false }))), 4000);
setTimeout(() => {
return store.dispatch(actions.setAnimTarget({
skill: sample(itemInfo.items.filter(i => i.skill)).item,
constructId: d[1].constructs[0].id,
player: false,
direction: 0,
}));
}, 500);
setTimeout(() => {
return store.dispatch(actions.setAnimTarget({
skill: sample(itemInfo.items.filter(i => i.skill)).item,
constructId: d[1].constructs[1].id,
player: true,
direction: 0,
}));
}, 3000);
return setTimeout(startDemo, 5000);
};
startDemo();
}
// store.subscribe(setInfo);
// store.on('SET_INFO', setInfo);
// events.on('SET_PLAYER', setInstance);
// events.on('SEND_SKILL', function skillActive(gameId, constructId, targetConstructId, skill) {
// ws.sendGameSkill(gameId, constructId, targetConstructId, skill);
// setConstructStatusUpdate(constructId, skill, targetConstructId);
// });
// events.on('CONSTRUCT_ACTIVE', function constructActiveCb(construct) {
// for (let i = 0; i < constructs.length; i += 1) {
// if (constructs[i].id === construct.id) constructs[i].active = !constructs[i].active;
// }
// return setConstructs(constructs);
// });
/* function errorPrompt(type) {
const message = errMessages[type];
const OK_BUTTON = '<button type="submit">OK</button>';
toast.error({
theme: 'dark',
color: 'black',
timeout: false,
drag: false,
position: 'center',
maxWidth: window.innerWidth / 2,
close: false,
buttons: [
[OK_BUTTON, (instance, thisToast) => instance.hide({ transitionOut: 'fadeOut' }, thisToast)],
],
message,
});
} */
// setup / localstorage
function urlHashChange() {
const { ws } = store.getState();
const cmds = querystring.parse(location.hash);
@ -348,6 +214,11 @@ function registerEvents(store) {
return true;
}
function startTutorial() {
store.dispatch(actions.setTutorial(1));
}
window.addEventListener('hashchange', urlHashChange, false);
return {
@ -356,12 +227,13 @@ function registerEvents(store) {
clearInstance,
clearMtxActive,
clearTutorial,
clearTutorialGame,
setAccount,
setAuthenticated,
setAccountInstances,
setActiveItem,
setActiveSkill,
setChatWheel,
setDemo,
setConstructList,
setNewConstruct,
setGame,
@ -377,6 +249,8 @@ function registerEvents(store) {
setSubscription,
setWs,
startTutorial,
urlHashChange,
notify,

View File

@ -10,18 +10,16 @@ function createReducer(defaultState, actionType) {
/* eslint-disable key-spacing */
module.exports = {
account: createReducer(null, 'SET_ACCOUNT'),
authenticated: createReducer(null, 'SET_AUTHENTICATED'),
activeItem: createReducer(null, 'SET_ACTIVE_VAR'),
activeSkill: createReducer(null, 'SET_ACTIVE_SKILL'),
animating: createReducer(false, 'SET_ANIMATING'),
animCb: createReducer(null, 'SET_ANIM_CB'),
animSkill: createReducer(null, 'SET_ANIM_SKILL'),
animSource: createReducer(null, 'SET_ANIM_SOURCE'),
animFocus: createReducer(null, 'SET_ANIM_FOCUS'),
animTarget: createReducer(null, 'SET_ANIM_TARGET'),
animText: createReducer(null, 'SET_ANIM_TEXT'),
demo: createReducer(null, 'SET_DEMO'),
resolution: createReducer(null, 'SET_RESOLUTION'),
chatShow: createReducer(null, 'SET_CHAT_SHOW'),
chatWheel: createReducer([], 'SET_CHAT_WHEEL'),

View File

@ -129,6 +129,7 @@ function createSocket(events) {
{ game_id: gameId, construct_id: constructId, target_construct_id: targetConstructId, skill },
]);
events.setActiveSkill(null);
events.clearTutorialGame();
}
function sendGameSkillClear(gameId) {
@ -255,10 +256,6 @@ function createSocket(events) {
events.setItemInfo(info);
}
function onDemo(v) {
events.setDemo(v);
}
let pongTimeout;
function onPong() {
events.setPing(Date.now() - ping);
@ -273,6 +270,7 @@ function createSocket(events) {
// this object wraps the reply types to a function
const handlers = {
AccountState: onAccount,
AccountAuthenticated: account => events.setAuthenticated(account),
AccountConstructs: onAccountConstructs,
AccountTeam: onAccountTeam,
AccountInstances: onAccountInstances,
@ -284,7 +282,6 @@ function createSocket(events) {
InstanceState: onInstanceState,
ItemInfo: onItemInfo,
Pong: onPong,
Demo: onDemo,
// QueueRequested: () => events.notify('PVP queue request received.'),
QueueRequested: () => true,
@ -303,6 +300,8 @@ function createSocket(events) {
ChatWheel: wheel => events.setChatWheel(wheel),
// Joining: () => events.notify('Searching for instance...'),
StartTutorial: () => events.startTutorial(),
Processing: () => true,
Error: errHandler,
};

View File

@ -115,8 +115,9 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 1) {
return (
<div class='info-item'>
<h2>Tutorial</h2>
<p> Welcome to the vbox phase tutorial.</p>
<h1>Welcome to MNML</h1>
<p> This is the <b>VBOX Phase</b> tutorial.</p>
<p> In the <b>VBOX Phase</b> you customise your constructs' skills and specialisations. </p>
<p> Colours are used to create powerful combinations with base items. </p>
<p> Buy the two colours from the store to continue. </p>
</div>
@ -126,9 +127,9 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 2) {
return (
<div class='info-item'>
<h2>Tutorial</h2>
<h2>Combining Items</h2>
<p> You start the game with the base <b>Attack</b> skill item. </p>
<p> Highlight all three items then click combine.</p>
<p> Highlight the <b>Attack</b> and the two <b> colours</b> then click <b> combine</b> </p>
</div>
);
}
@ -137,11 +138,11 @@ function tutorialStage(tutorial, clearTutorial, instance) {
const constructOne = instance.players[0].constructs[0].name;
return (
<div class='info-item'>
<h2>Tutorial</h2>
<h2>Equipping Items</h2>
<p> The first construct on your team is <b>{constructOne}</b>. </p>
<p> Skill items can be equipped to your constructs to be used in the combat phase. </p>
<p> Click your new skill from the stash. <br />
Once selected click the flashing <b>SKILL</b> slot to equip the skill. </p>
Once selected click the flashing <b>SKILL</b> slot or the construct img to equip the skill. </p>
</div>
);
}
@ -149,7 +150,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 4) {
return (
<div class='info-item'>
<h2>Tutorial</h2>
<h2>Specialisations</h2>
<p> You can also buy specialisation items for your constructs. <br />
Specialisation items increase stats including power, speed and life. </p>
<p> Buy the specialisation item from the store to continue. </p>
@ -160,11 +161,12 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 5) {
return (
<div class='info-item'>
<h2>Tutorial</h2>
<h2>Specialisations</h2>
<p> Equipping specialisation items will increase the stats of your constructs.</p>
<p> These can also be combined with colours for further specialisation. </p>
<p> Click the specialisation item in the stash.<br />
Once selected click the flashing <b>SPEC</b> slot to equip the specialisation. </p>
<p> <b>PRO TIP:</b> while selecting an item in the shop, click on your construct to buy and equip in one step. </p>
</div>
);
}
@ -174,11 +176,12 @@ function tutorialStage(tutorial, clearTutorial, instance) {
const constructThree = instance.players[0].constructs[2].name;
return (
<div class='info-item'>
<h2>Tutorial</h2>
<h2>Skills</h2>
<p> You have now created a construct with an upgraded skill and base spec. </p>
<p> The goal is to create three powerful constructs for combat. </p>
<p> Equip your other constructs <b>{constructTwo}</b> and <b>{constructThree}</b> with the Attack skill. <br />
Ensure each construct has a single skill to continue. </p>
<p> <b>PRO TIP:</b> Select a skill or spec on a construct and click another construct to swap it. </p>
</div>
);
}
@ -186,7 +189,7 @@ function tutorialStage(tutorial, clearTutorial, instance) {
if (tutorial === 7) {
return (
<div class='info-item'>
<h2>Tutorial</h2>
<h2>Economy</h2>
<p> Each round you start with 30 bits and a store full of different skills, specs and colours. </p>
<p> Bits are your currency for buying items. <br />
You can refill the store by pressing the refill button for 2b. <br />
@ -203,22 +206,22 @@ function tutorialStage(tutorial, clearTutorial, instance) {
return (
<div class='info-item'>
<h2>Tutorial</h2>
<h2>GLHF</h2>
<p>That completes the VBOX Tutorial.</p>
<p>Press <b>READY</b> to progress to the <b>GAME PHASE</b> <br />
You can continue creating new items to upgrade your constructs further. </p>
<p>Press the green <b>READY</b> button in the bottom right to progress to the <b>GAME PHASE</b> <br />
or continue creating new items to upgrade your constructs further. </p>
</div>
);
}
return false;
};
const classes = tutorial === 8 ? 'focus' : '';
const text = tutorial === 8 ? 'Continue' : 'Skip Tutorial'
const exitTutorial = <button
class={classes}
onClick={e => e.stopPropagation()}
onMouseDown={exit}> {text} </button>;
const exitTutorial = tutorial === 8 ?
<button
class='focus'
onClick={e => e.stopPropagation()}
onMouseDown={exit}> Continue </button>
: null;
return (
<div class='tutorial'>

View File

@ -240,28 +240,16 @@ function convertItem(v) {
}
function effectInfo(i) {
// FIX ME
const hybridBlast = 25;
const hasteStrike = 30;
function multiplier(s) { // Update later to use server info in future
if (s === 'CounterAttack') return 120;
if (s === 'CounterAttack+') return 160;
if (s === 'CounterAttack++') return 230;
if (s === 'DecayTick') return 33;
if (s === 'DecayTick+') return 45;
if (s === 'DecayTick++') return 70;
if (s === 'SiphonTick') return 25;
if (s === 'SiphonTick+') return 30;
if (s === 'SiphonTick++') return 40;
if (s === 'TriageTick') return 75;
if (s === 'TriageTick+') return 110;
if (s === 'TriageTick++') return 140;
if (s === 'Electrocute' || s === 'ElectrocuteTick') return 80;
if (s === 'Electrocute+' || s === 'ElectrocuteTick+') return 100;
if (s === 'Electrocute++' || s === 'ElectrocuteTick++') return 130;
if (s === 'CounterAttack') return 115;
if (s === 'CounterAttack+') return 130;
if (s === 'CounterAttack++') return 160;
if (s === 'Electrocute') return 80;
if (s === 'Electrocute+') return 90;
if (s === 'Electrocute++') return 100;
return 0;
}
@ -286,13 +274,13 @@ function effectInfo(i) {
case 'Vulnerable': return `Construct will take ${i.meta[1] - 100}% increased red damage`;
case 'Silence': return 'Disable construct from casting any blue skills';
case 'Wither': return `Construct will take ${100 - i.meta[1]}% reduced healing`; //
case 'Decay': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower as blue damage each turn.`; //
case 'Decay': return `Construct will take ${i.meta[1].amount} blue damage each turn.`; //
case 'Electric': return `Attacks against this construct will apply Electrocute dealing ${multiplier(i.meta[1])}% of construct BluePower as blue damage each turn.`;
case 'Electrocute': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower as blue damage each turn.`;
case 'Electrocute': return `Construct will take ${i.meta[1].amount} blue damage each turn.`;
case 'Absorb': return 'If construct takes damage, Absorption will be applied increasing RedPower and BluePower based on damage taken.';
case 'Absorption': return `Increasing construct RedPower and BluePower by ${i.meta[1]}`;
case 'Triage': return `Construct will be healed for ${multiplier(i.tick.skill)}% of caster's GreenPower each turn.`;
case 'Siphon': return `Construct will take ${multiplier(i.tick.skill)}% of caster's BluePower + GreenPower as blue damage each turn, healing the caster.`;
case 'Triage': return `Construct will be healed for ${i.meta[1].amount} green life each turn.`;
case 'Siphon': return `Construct will take ${i.meta[1].amount} blue damage each turn, healing the caster.`;
default: return 'Missing Effect Text';
}

3
core/.cargo/config Executable file
View File

@ -0,0 +1,3 @@
[target.x86_64-pc-windows-msvc.gnu]
rustc-link-search = ["C:\\Program Files\\PostgreSQL\\pg96\\lib"]

4
core/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
target/
Cargo.lock
log/
.env

13
core/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "mnml_core"
version = "1.11.0"
authors = ["ntr <ntr@smokestack.io>", "mashy <mashy@mnml.gg>"]
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
failure = "0.1"
log = "0.4"
rand = "0.6"
serde = "1"
serde_derive = "1"
uuid = { version = "0.5", features = ["serde", "v4"] }

5
core/fixme.md Normal file
View File

@ -0,0 +1,5 @@
# FIXME
game ready not auto starting resolve phase
remove big header and move to rhs of news pane
add big logo w/ noise when you mouseover stuff etc

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,9 @@
use construct::{Stat, EffectMeta};
use game::{Colour};
use skill::{Skill};
use util::{IntPct};
pub type Cooldown = Option<u8>;
pub type Cooldown = Option<usize>;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Effect {
@ -41,10 +42,17 @@ pub enum Effect {
// effects over time
Triage,
Decay,
Regen,
Siphon,
Triaged, // immunity
Decay,
Decayed, // immunity
Siphon,
Siphoned, // immunity
Countered,
// Regen,
// Airborne,
// Boost
// Bleed,
@ -66,18 +74,42 @@ impl Effect {
pub fn immune(&self, skill: Skill) -> bool {
match self {
Effect::Banish => true,
Effect::Sustain => [
Skill::Stun,
Skill::Silence,
Skill::SilencePlus,
Skill::SilencePlusPlus,
Skill::Ruin,
Skill::RuinPlus,
Skill::RuinPlusPlus,
Skill::Restrict,
Skill::RestrictPlus,
Skill::RestrictPlusPlus
// these provide immunity for the ticks
// the base skills will still resolve
// but they have early return checks
// to ensure the effect is reapplied but damage is not
Effect::Siphoned => [
Skill::SiphonTick,
].contains(&skill),
Effect::Decayed => [
Skill::DecayTick,
].contains(&skill),
Effect::Triaged => [
Skill::TriageTick,
].contains(&skill),
Effect::Countered => [
Skill::CounterAttack,
Skill::CounterAttackPlus,
Skill::CounterAttackPlusPlus,
].contains(&skill),
_ => false,
}
}
// hidden effects are used generally for immunities
// they are not displayed on client
// and not included in counts
pub fn hidden(&self) -> bool {
match self {
Effect::Siphoned => true,
Effect::Decayed => true,
Effect::Triaged => true,
Effect::Countered => true,
_ => false,
}
}
@ -101,7 +133,7 @@ impl Effect {
pub fn modifications(&self) -> Vec<Stat> {
match self {
// Bases
Effect::Block => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken],
Effect::Block => vec![Stat::RedDamageReceived, Stat::BlueDamageReceived],
Effect::Buff => vec![Stat::BluePower, Stat::RedPower, Stat::Speed],
Effect::Slow => vec![Stat::Speed],
@ -111,10 +143,10 @@ impl Effect {
Effect::Hybrid => vec![Stat::GreenPower],
// Damage taken changes
Effect::Curse => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken],
Effect::Pure => vec![Stat::GreenDamageTaken], // increased green taken
Effect::Vulnerable => vec![Stat::RedDamageTaken],
Effect::Wither => vec![Stat::GreenDamageTaken], // reduced green taken
Effect::Curse => vec![Stat::RedDamageReceived, Stat::BlueDamageReceived],
Effect::Pure => vec![Stat::GreenHealingReceived], // increased green taken
Effect::Vulnerable => vec![Stat::RedDamageReceived],
Effect::Wither => vec![Stat::GreenHealingReceived], // reduced green taken
// Speed
Effect::Haste => vec![Stat::Speed],
@ -123,7 +155,7 @@ impl Effect {
}
}
pub fn apply(&self, value: u64, meta: Option<EffectMeta>) -> u64 {
pub fn apply(&self, value: usize, meta: Option<EffectMeta>) -> usize {
match self {
Effect::Amplify |
Effect::Vulnerable |
@ -154,7 +186,10 @@ impl Effect {
}
}
pub fn colour(&self) -> Option<Colour> {
// Old colour matching system for buffs / debuffs
// Had issues as some effects will be considered as both a buff and debuff e.g. invert,
// Ended up being confusing with mismatch skills that have red / blue e.g. amplify, haste, hybrid
/* pub fn colour(&self) -> Option<Colour> {
match self {
// physical
Effect::Stun => Some(Colour::Red),
@ -192,18 +227,14 @@ impl Effect {
// effects over time
Effect::Triage => Some(Colour::Green),
Effect::Decay => Some(Colour::Blue),
Effect::Regen => Some(Colour::Green),
Effect::Siphon => Some(Colour::Blue),
Effect::Pure => Some(Colour::Green),
Effect::Triaged => None,
Effect::Decayed => None,
Effect::Siphoned => None,
Effect::Countered => None,
Effect::Ko => None,
}
}
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Colour {
Red,
Blue,
Green,
}*/
}

2514
core/src/game.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,8 @@
use std::fs::File;
use std::collections::{HashMap};
use uuid::Uuid;
use serde_cbor::{from_slice, to_vec};
use postgres::transaction::Transaction;
use failure::Error;
use failure::err_msg;
@ -14,16 +10,11 @@ use failure::err_msg;
use chrono::prelude::*;
use chrono::Duration;
use account::Account;
use player::{Player, Score};
use game::{Game};
use item::{Item};
use vbox;
use player::{Player, Score, player_create};
use mob::{bot_player, instance_mobs};
use game::{Game, Phase, game_get, game_write, game_update};
use item::{Item};
use rpc::{RpcMessage};
use img;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
enum InstancePhase {
@ -107,14 +98,14 @@ pub struct Instance {
time_control: TimeControl,
phase: InstancePhase,
phase_end: Option<DateTime<Utc>>,
phase_start: DateTime<Utc>,
pub phase_end: Option<DateTime<Utc>>,
pub phase_start: DateTime<Utc>,
winner: Option<Uuid>,
}
impl Instance {
fn new() -> Instance {
pub fn new() -> Instance {
Instance {
id: Uuid::new_v4(),
players: vec![],
@ -152,8 +143,8 @@ impl Instance {
.collect::<Vec<Uuid>>()
}
// time out lobbies that have been open too long
pub fn upkeep(mut self) -> (Instance, Option<Game>) {
// time out lobbies that have been open too long
if self.phase == InstancePhase::Lobby && self.phase_timed_out() {
self.finish();
return (self, None);
@ -178,7 +169,7 @@ impl Instance {
(self, new_game)
}
fn set_name(mut self, name: String) -> Result<Instance, Error> {
pub fn set_name(mut self, name: String) -> Result<Instance, Error> {
if name.len() == 0 {
return Err(err_msg("name must have a length"));
}
@ -187,12 +178,12 @@ impl Instance {
Ok(self)
}
fn set_time_control(mut self, tc: TimeControl) -> Instance {
pub fn set_time_control(mut self, tc: TimeControl) -> Instance {
self.time_control = tc;
self
}
fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> {
pub fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> {
if self.players.len() >= self.max_players {
return Err(err_msg("game full"))
}
@ -206,7 +197,7 @@ impl Instance {
Ok(self)
}
fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, Error> {
pub fn player_ready(&mut self, player_id: Uuid) -> Result<Option<Game>, Error> {
if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) {
return Err(err_msg("instance not in start or vbox phase"));
}
@ -308,7 +299,7 @@ impl Instance {
self.next_round()
}
fn next_round(&mut self) -> &mut Instance {
pub fn next_round(&mut self) -> &mut Instance {
if self.finish_condition() {
return self.finish();
}
@ -350,7 +341,7 @@ impl Instance {
self
}
fn finished(&self) -> bool {
pub fn finished(&self) -> bool {
self.phase == InstancePhase::Finished
}
@ -378,7 +369,7 @@ impl Instance {
self
}
fn current_game_id(&self) -> Option<Uuid> {
pub fn current_game_id(&self) -> Option<Uuid> {
if self.phase != InstancePhase::InProgress {
return None;
}
@ -394,7 +385,7 @@ impl Instance {
return current_round.game_id;
}
fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
pub fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> {
{
let current_round = self.rounds
.iter_mut()
@ -436,14 +427,14 @@ impl Instance {
}
// PLAYER ACTIONS
fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> {
pub fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> {
self.players
.iter_mut()
.find(|p| p.id == account)
.ok_or(err_msg("account not in instance"))
}
fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> {
pub fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> {
self.players
.iter_mut()
.find(|p| p.id != account)
@ -501,334 +492,18 @@ impl Instance {
Ok(self)
}
pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct_id: Option<Uuid>) -> Result<Instance, Error> {
pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct: Option<Uuid>) -> Result<Instance, Error> {
self.vbox_action_allowed(account)?;
self.account_player(account)?
.vbox_unequip(target, construct_id, target_construct_id)?;
.vbox_unequip(target, construct_id, target_construct)?;
Ok(self)
}
}
pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
let instance_bytes = to_vec(&instance)?;
let query = "
INSERT INTO instances (id, data, upkeep)
VALUES ($1, $2, $3)
RETURNING id;
";
let result = tx
.query(query, &[&instance.id, &instance_bytes, &instance.phase_end])?;
result.iter().next().ok_or(format_err!("no instances written"))?;
return Ok(instance);
}
pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result<Instance, Error> {
let instance_bytes = to_vec(&instance)?;
let query = "
UPDATE instances
SET data = $1, finished = $2, upkeep = $3, updated_at = now()
WHERE id = $4
RETURNING id, data;
";
let result = tx
.query(query, &[&instance_bytes, &instance.finished(), &instance.phase_end, &instance.id])?;
result.iter().next().ok_or(err_msg("no instance row returned"))?;
trace!("{:?} wrote instance", instance.id);
if instance.finished() {
info!("finished id={:?}", instance.id);
match instance_json_file_write(&instance) {
Ok(dest) => info!("wrote dest={:?}", dest),
Err(e) => error!("json write error={:?}", e),
};
}
return Ok(instance);
}
fn instance_json_file_write(g: &Instance) -> Result<String, Error> {
let dest = format!("/var/lib/mnml/data/instances/{}.mnml.instance.json", g.id);
serde_json::to_writer(File::create(&dest)?, g)?;
Ok(dest)
}
pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result<Instance, Error> {
let query = "
SELECT *
FROM instances
WHERE id = $1
FOR UPDATE;
";
let result = tx
.query(query, &[&instance_id])?;
let returned = match result.iter().next() {
Some(row) => row,
None => return Err(err_msg("instance not found")),
};
let instance_bytes: Vec<u8> = returned.get("data");
let instance = from_slice::<Instance>(&instance_bytes)?;
return Ok(instance);
}
pub fn instance_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
let query = "
DELETE
FROM instances
WHERE id = $1;
";
let result = tx
.execute(query, &[&id])?;
if result != 1 {
return Err(format_err!("unable to delete instance {:?}", id));
}
info!("instance deleted {:?}", id);
return Ok(());
}
pub fn _instance_list(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
let query = "
SELECT data, id
FROM instances
AND finished = false;
";
let result = tx
.query(query, &[])?;
let mut list = vec![];
for row in result.into_iter() {
let bytes: Vec<u8> = row.get(0);
let id = row.get(1);
match from_slice::<Instance>(&bytes) {
Ok(i) => list.push(i),
Err(_e) => {
instance_delete(tx, id)?;
}
};
}
return Ok(list);
}
pub fn instances_need_upkeep(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
let query = "
SELECT data, id
FROM instances
WHERE finished = false
AND upkeep < now()
FOR UPDATE;
";
let result = tx
.query(query, &[])?;
let mut list = vec![];
for row in result.into_iter() {
let bytes: Vec<u8> = row.get(0);
let id = row.get(1);
match from_slice::<Instance>(&bytes) {
Ok(i) => list.push(i),
Err(_e) => {
instance_delete(tx, id)?;
}
};
}
return Ok(list);
}
// timed out instances with no time control
pub fn instances_idle(tx: &mut Transaction) -> Result<Vec<Instance>, Error> {
let query = "
SELECT data, id
FROM instances
WHERE finished = false
AND updated_at < now() - interval '1 hour'
FOR UPDATE;
";
let result = tx
.query(query, &[])?;
let mut list = vec![];
for row in result.into_iter() {
let bytes: Vec<u8> = row.get(0);
let id = row.get(1);
match from_slice::<Instance>(&bytes) {
Ok(i) => list.push(i),
Err(_e) => {
instance_delete(tx, id)?;
}
};
}
return Ok(list);
}
pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result<Instance, Error> {
let bot = bot_player();
let bot_id = bot.id;
// generate bot imgs for the client to see
for c in bot.constructs.iter() {
img::shapes_write(c.img)?;
}
let mut instance = Instance::new()
.set_time_control(TimeControl::Practice)
.set_name(bot.name.clone())?;
let player = Player::from_account(tx, account)?;
instance.add_player(player.clone())?;
instance.add_player(bot)?;
instance.player_ready(bot_id)?;
instance = instance_create(tx, instance)?;
player_create(tx, player, instance.id, account)?;
Ok(instance)
}
pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result<Instance, Error> {
let mut instance = Instance::new()
// TODO generate nice game names
.set_name("PVP".to_string())?;
instance = instance_create(tx, instance)?;
for account in [a, b].iter() {
let acc_p = Player::from_account(tx, &account)?;
let player = player_create(tx, acc_p, instance.id, account)?;
instance.add_player(player)?;
}
instance_update(tx, instance)
}
pub fn instance_abandon(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<RpcMessage, Error> {
let mut instance = instance_get(tx, instance_id)?;
if let Some(game_id) = instance.current_game_id() {
let mut game = game_get(tx, game_id)?;
game.player_by_id(account.id)?.forfeit();
game = game.start(); // actually finishes it...
game_update(tx, &game)?;
}
instance.account_player(account.id)?.set_lose();
instance.account_opponent(account.id)?.set_win();
instance.next_round();
Ok(RpcMessage::InstanceState(instance_update(tx, instance)?))
}
pub fn instance_ready(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result<RpcMessage, Error> {
let mut instance = instance_get(tx, instance_id)?;
let player_id = instance.account_player(account.id)?.id;
if let Some(game) = instance.player_ready(player_id)? {
game_write(tx, &game)?;
// ensures cleanup for warden etc is done
game_update(tx, &game)?;
instance_update(tx, instance)?;
return Ok(RpcMessage::GameState(game));
}
Ok(RpcMessage::InstanceState(instance_update(tx, instance)?))
}
pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result<RpcMessage, Error> {
let instance = instance_get(tx, instance_id)?;
if let Some(game_id) = instance.current_game_id() {
let game = game_get(tx, game_id)?;
// return the game until it's finished
if game.phase != Phase::Finished {
return Ok(RpcMessage::GameState(game))
}
}
Ok(RpcMessage::InstanceState(instance))
}
pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> {
let mut instance = instance_get(tx, instance_id)?;
instance.game_finished(game)?;
// info!("{:?}", instance_get(tx, instance_id)?);
instance_update(tx, instance)?;
Ok(())
}
pub fn bot_instance() -> Instance {
let mut instance = Instance::new();
let bot_player = bot_player();
let bot = bot_player.id;
instance.add_player(bot_player).unwrap();
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
instance.add_player(player).expect("could not add player");
instance.player_ready(player_account).unwrap();
instance.player_ready(bot).unwrap();
return instance;
}
pub fn demo() -> Result<Vec<Player>, Error> {
let bot = bot_player();
// generate bot imgs for the client to see
for c in bot.constructs.iter() {
img::shapes_write(c.img)?;
};
let bot2 = bot_player();
// generate bot imgs for the client to see
for c in bot2.constructs.iter() {
img::shapes_write(c.img)?;
};
Ok(vec![bot, bot2])
}
#[cfg(test)]
mod tests {
use super::*;
use mob::{bot_player, instance_mobs};
#[test]
fn instance_pve_test() {
@ -854,7 +529,7 @@ mod tests {
let _instance = Instance::new();
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let _player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true);
let _player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true);
}
#[test]
@ -865,7 +540,7 @@ mod tests {
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"a".to_string(), constructs);
let player = Player::new(player_account, None, &"a".to_string(), constructs);
let a_id = player.id;
instance.add_player(player).expect("could not add player");
@ -873,7 +548,7 @@ mod tests {
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"b".to_string(), constructs);
let player = Player::new(player_account, None, &"b".to_string(), constructs);
let b_id = player.id;
instance.add_player(player).expect("could not add player");
@ -902,7 +577,7 @@ mod tests {
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"a".to_string(), constructs);
let player = Player::new(player_account, None, &"a".to_string(), constructs);
let a_id = player.id;
instance.add_player(player).expect("could not add player");
@ -910,7 +585,7 @@ mod tests {
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"b".to_string(), constructs);
let player = Player::new(player_account, None, &"b".to_string(), constructs);
let b_id = player.id;
instance.add_player(player).expect("could not add player");
@ -942,7 +617,7 @@ mod tests {
let player_account = Uuid::new_v4();
let constructs = instance_mobs(player_account);
let player = Player::new(player_account, &"a".to_string(), constructs);
let player = Player::new(player_account, None, &"a".to_string(), constructs);
let _a_id = player.id;
instance.add_player(player).expect("could not add player");

Some files were not shown because too many files have changed in this diff Show More