diff options
Diffstat (limited to 'src')
55 files changed, 4161 insertions, 0 deletions
diff --git a/src/a_brood.moon b/src/a_brood.moon new file mode 100644 index 0000000..4c94ca6 --- /dev/null +++ b/src/a_brood.moon @@ -0,0 +1,42 @@ +reg = require "ability_reg" +import Ability from reg + +class Brood extends Ability + @text = "Brood" + @description = "brood" + @hits_icon = {1,1,1,1,0,0,0,0} + @speed = 5 + @distance = 1 + new: (...)=> + super(...) + @requirements = { + {"consume_stat", "stamina", 1}, + {"status", "active"}, + {"distance", 1}, + } + target: (world, party, char) => + room = world.player_party.room + enemy_party = room.parties[1] + if enemy_party == party + enemy_party = room.parties[2] + use: (world, party, char)-> + for i = 1,4 + chars_at_loc = world.room.data.locations[i] + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.player_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 1) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,i, hp_minus) + load: ()=> + print("TODO!") + + unload:()=> + print("TODO!") diff --git a/src/a_dance.moon b/src/a_dance.moon new file mode 100644 index 0000000..1b07ddb --- /dev/null +++ b/src/a_dance.moon @@ -0,0 +1,49 @@ +reg = require "ability_reg" +ui = require "ui" +import Ability from reg + +mod = ... + +class Dance extends Ability + @text = "Dance" + @description = "Shake your body!" + @hits_icon = {0,0,0,0,0,1,1,0} + @sprite = "data/body-balance.png" + @speed = 7 + @distance = 1 + new: (...)=> + super("Dance",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + for _,i in pairs({6,7}) + chars_at_loc = world.room.data.locations[i] + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 1) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,1, hp_minus) + +mod.Tumble = Tumble + +mod diff --git a/src/a_drum.moon b/src/a_drum.moon new file mode 100644 index 0000000..4464816 --- /dev/null +++ b/src/a_drum.moon @@ -0,0 +1,48 @@ +reg = require "ability_reg" +ui = require "ui" +import Ability from reg + +mod = ... + +class Drum extends Ability + @text = "Drum" + @description = "Rat-a-tat-tat!" + @hits_icon = {0,0,0,0,1,0,0,0} + @sprite = "data/drum.png" + @speed = 5 + @distance = 1 + new: (...)=> + super("Drum",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + chars_at_loc = world.room.data.locations[5] + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 1) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,1, hp_minus) + +mod.Tumble = Tumble + +mod diff --git a/src/a_firebreath.moon b/src/a_firebreath.moon new file mode 100644 index 0000000..e8c9f73 --- /dev/null +++ b/src/a_firebreath.moon @@ -0,0 +1,51 @@ +reg = require "ability_reg" +import Ability from reg + +mod = ... +print("In tubmle, reg is",reg) + +class FireBreath extends Ability + @text = "Breath Fire" + @description = "Breath fire, visible for everyone!" + @sprite = "data/dragon-breath.png" + @hits_icon = {0,0,0,0,1,1,1,1} + @speed = 10 + @distance = 1 + new: (...)=> + super("FireBreath",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + print("Doing FireBreath") + for i = 5,8 + chars_at_loc = world.room.data.locations[i] + print("chars at loc:",chars_at_loc) + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 1) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,i - 4, hp_minus) + +mod.Tumble = Tumble + +mod diff --git a/src/a_hackysacks.moon b/src/a_hackysacks.moon new file mode 100644 index 0000000..2939c80 --- /dev/null +++ b/src/a_hackysacks.moon @@ -0,0 +1,50 @@ +reg = require "ability_reg" +import Ability from reg + +mod = ... +print("In tubmle, reg is",reg) + +class Juggle extends Ability + @text = "Juggle Balls" + @description = "Juggle some balls" + @hits_icon = {0,0,0,0,1,1,0,0} + @sprite = "data/juggler.png" + @speed = 4 + @distance = 1 + new: (...)=> + super("Juggle",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + for i = 5,6 + chars_at_loc = world.room.data.locations[i] + print("chars at loc:",chars_at_loc) + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 1) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,i-4, hp_minus) + +mod.Tumble = Tumble + +mod diff --git a/src/a_highjump.moon b/src/a_highjump.moon new file mode 100644 index 0000000..878dc4f --- /dev/null +++ b/src/a_highjump.moon @@ -0,0 +1,49 @@ +reg = require "ability_reg" +ui = require "ui" +import Ability from reg + +mod = ... + +class HighJump extends Ability + @text = "High Jump" + @description = "Jump up high. Like REALLY high." + @hits_icon = {0,0,0,0,0,0,1,1} + @sprite = "data/kangaroo.png" + @speed = 3 + @distance = 1 + new: (...)=> + super("Tumble",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + for _,i in pairs({7,8}) + chars_at_loc = world.room.data.locations[i] + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 1) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,1, hp_minus) + +mod.Tumble = Tumble + +mod diff --git a/src/a_knifeslip.moon b/src/a_knifeslip.moon new file mode 100644 index 0000000..89c8b55 --- /dev/null +++ b/src/a_knifeslip.moon @@ -0,0 +1,51 @@ +reg = require "ability_reg" +import Ability from reg + +mod = ... +print("In tubmle, reg is",reg) + +class KnifeSlip extends Ability + @text = "Juggle Knives" + @description = "Juggle some knives for\nthe people in the back" + @hits_icon = {0,0,0,0,0,0,1,1} + @sprite = "data/thrown-knife.png" + @speed = 4 + @distance = 1 + new: (...)=> + super("KnifeSlip",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + print("Doing knifeslip") + for i = 7,8 + chars_at_loc = world.room.data.locations[i] + print("chars at loc:",chars_at_loc) + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 2) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,i-4, hp_minus) + +mod.Tumble = Tumble + +mod diff --git a/src/a_mope.moon b/src/a_mope.moon new file mode 100644 index 0000000..8ea4cd1 --- /dev/null +++ b/src/a_mope.moon @@ -0,0 +1,41 @@ +reg = require "ability_reg" +import Ability from reg + +class Mope extends Ability + @text = "Mope" + @description = "mope" + @hits_icon = {0,0,0,1,0,0,0,0} + @speed = 5 + @distance = 1 + new: (...)=> + super(...) + @requirements = { + {"consume_stat", "stamina", 1}, + {"status", "active"}, + {"distance", 1}, + } + target: (world, party, char) => + room = world.player_party.room + enemy_party = room.parties[1] + if enemy_party == party + enemy_party = room.parties[2] + use: (world, party, char)-> + chars_at_loc = world.room.data.locations[4] + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.player_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 2) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,1, hp_minus) + load: ()=> + print("TODO!") + + unload:()=> + print("TODO!") diff --git a/src/a_pass.moon b/src/a_pass.moon new file mode 100644 index 0000000..1f9ed21 --- /dev/null +++ b/src/a_pass.moon @@ -0,0 +1,34 @@ + +reg = require "ability_reg" +import Ability from reg + +mod = ... + +class Pass extends Ability + @text = "Pass" + @description = "Do nothing..." + @hits_icon = {0,0,0,0,0,0,0,0} + @speed = 0 + @sprite = "data/no_action.png" + new: (...)=> + super("Pass",{}) + @requirements = {} + target: (world, party, char) => + nil + + load: () => + main = require "main" + infocard = am.group!\tag("infocard") + main.root("screen")\append(infocard) + + unload: () => + main = require "main" + infocard = main.root("infocard") + main.root("screen")\remove(infocard) + + use: (world, party, char)=> + print("Pass used") + +mod.Pass = Pass + +mod diff --git a/src/a_physique.moon b/src/a_physique.moon new file mode 100644 index 0000000..6f1b94b --- /dev/null +++ b/src/a_physique.moon @@ -0,0 +1,48 @@ +reg = require "ability_reg" +ui = require "ui" +import Ability from reg + +mod = ... + +class Physique extends Ability + @text = "Physique" + @description = "Big ol' muscles" + @hits_icon = {0,0,0,0,0,1,0,0} + @sprite = "data/strong-man.png" + @speed = 3 + @distance = 1 + new: (...)=> + super("Physique",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + chars_at_loc = world.room.data.locations[6] + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 2) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,2, hp_minus) + +mod.Tumble = Tumble + +mod diff --git a/src/a_rat_bite.moon b/src/a_rat_bite.moon new file mode 100644 index 0000000..9c55269 --- /dev/null +++ b/src/a_rat_bite.moon @@ -0,0 +1,42 @@ +reg = require "ability_reg" +import Ability from reg + +class RatBite extends Ability + @text = "Rat Bite" + @description = "A rat's bite" + @hits_icon = {0,0,0,0,1,0,0,0} + @speed = 3 + @distance = 1 + new: (...)=> + super(...) + @requirements = { + {"consume_stat", "stamina", 1}, + {"status", "active"}, + {"distance", 1}, + } + target: (world, party, char) => + room = world.player_party.room + enemy_party = room.parties[1] + if enemy_party == party + enemy_party = room.parties[2] + use: (world, party, char)-> + chars_at_loc = world.room.data.locations[4] + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.player_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 1) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,1, hp_minus) + print("rat bite done, chars_at_loc:",chars_at_loc) + load: ()=> + print("TODO!") + + unload:()=> + print("TODO!") diff --git a/src/a_rat_scurry.moon b/src/a_rat_scurry.moon new file mode 100644 index 0000000..cd00e0a --- /dev/null +++ b/src/a_rat_scurry.moon @@ -0,0 +1,25 @@ +reg = require "ability_reg" +import Ability from reg + +class RatScurry extends Ability + @text = "Scurry" + @speed = 5 + @distance = 1 + new: (...)=> + super(...) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + enemy_party = room.parties[1] + if enemy_party == party + enemy_party = room.parties[2] + use: (world, party, char)-> + print("Rat scurry used") + + load: ()=> + print("TODO!") + + unload:()=> + print("TODO!") diff --git a/src/a_ruminate.moon b/src/a_ruminate.moon new file mode 100644 index 0000000..cf84eef --- /dev/null +++ b/src/a_ruminate.moon @@ -0,0 +1,29 @@ +reg = require "ability_reg" +import Ability from reg + +class Ruminate extends Ability + @text = "Ruminate" + @description = "Ruminate" + @hits_icon = {0,0,0,0,1,1,1,1} + @speed = 5 + @distance = 1 + new: (...)=> + super(...) + @requirements = { + {"consume_stat", "stamina", 1}, + {"status", "active"}, + {"distance", 1}, + } + target: (world, party, char) => + room = world.player_party.room + enemy_party = room.parties[1] + if enemy_party == party + enemy_party = room.parties[2] + use: (world, party, char)-> + for _, member in pairs(party.members) + char\set_field("hp",char.data.hp + 1) + load: ()=> + print("TODO!") + + unload:()=> + print("TODO!") diff --git a/src/a_strum.moon b/src/a_strum.moon new file mode 100644 index 0000000..392a582 --- /dev/null +++ b/src/a_strum.moon @@ -0,0 +1,37 @@ +reg = require "ability_reg" +import Ability from reg + +mod = ... +print("In tubmle, reg is",reg) + +class Strum extends Ability + @text = "Strum" + @description = "Strum a cord to heal the troupe!" + @hits_icon = {1,1,1,1,0,0,0,0} + @sprite = "data/g-clef.png" + @speed = 1 + @distance = 1 + new: (...)=> + super("Strum",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + print("Doing Strum, party was", party) + for _, member in pairs(party.members) + member\set_field("hp",member.data.hp + 1) + +mod.Tumble = Tumble + +mod diff --git a/src/a_sulk.moon b/src/a_sulk.moon new file mode 100644 index 0000000..bf5d0a3 --- /dev/null +++ b/src/a_sulk.moon @@ -0,0 +1,43 @@ +reg = require "ability_reg" +import Ability from reg + +class Sulk extends Ability + @text = "Sulk" + @description = "sulk" + @hits_icon = {0,0,1,1,0,0,0,0} + @speed = 3 + @distance = 1 + new: (...)=> + super(...) + @requirements = { + {"consume_stat", "stamina", 1}, + {"status", "active"}, + {"distance", 1}, + } + target: (world, party, char) => + room = world.player_party.room + enemy_party = room.parties[1] + if enemy_party == party + enemy_party = room.parties[2] + use: (world, party, char)-> + for i = 3,4 + chars_at_loc = world.room.data.locations[i] + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.player_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 1) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,8 - i, hp_minus) + print("rat bite done, chars_at_loc:",chars_at_loc) + load: ()=> + print("TODO!") + + unload:()=> + print("TODO!") diff --git a/src/a_test.moon b/src/a_test.moon new file mode 100644 index 0000000..5c8f1b0 --- /dev/null +++ b/src/a_test.moon @@ -0,0 +1,52 @@ +reg = require "ability_reg" +ui = require "ui" +import Ability from reg + +mod = ... +print("In tubmle, reg is",reg) + +class Everything extends Ability + @text = "Everything" + @description = "destroy everything" + @hits_icon = {1,1,1,1,1,1,1,1} + @sprite = "data/tumble.png" + @speed = 5 + @distance = 1 + new: (...)=> + super("Everything",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + for i = 5,8 + print("Doing tumble") + chars_at_loc = world.room.data.locations[i] + print("chars at loc:",chars_at_loc) + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 10) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,i-4, hp_minus) + +mod.Test = Test + +mod diff --git a/src/a_tumble.moon b/src/a_tumble.moon new file mode 100644 index 0000000..d3c38bd --- /dev/null +++ b/src/a_tumble.moon @@ -0,0 +1,51 @@ +reg = require "ability_reg" +ui = require "ui" +import Ability from reg + +mod = ... +print("In tubmle, reg is",reg) + +class Tumble extends Ability + @text = "Tumble" + @description = "Tumble around for those nearby" + @hits_icon = {0,0,0,0,1,0,0,0} + @sprite = "data/tumble.png" + @speed = 5 + @distance = 1 + new: (...)=> + super("Tumble",{}) + @requirements = { + {"status", "active"}, + } + target: (world, party, char) => + room = world.player_party.room + my_pos = char.location + --search outward for a target + char_tbl1, char_tbl2 = nil, nil + for distance = 1, 8 + char_tbl1 = room\at_location(my_pos + distance) + char_tbl2 = room\at_location(my_pos - distance) + if (char_tbl1 and #char_tbl1 > 0) or (char_tbl2 and #char_tbl2 > 0) + break + + use: (world, party, char)-> + print("Doing tumble") + chars_at_loc = world.room.data.locations[5] + print("chars at loc:",chars_at_loc) + if world.server + a_chars_at_loc = {} + for _,v in ipairs(chars_at_loc) + table.insert(a_chars_at_loc,world.enemy_party\member(v.uname)) + chars_at_loc = a_chars_at_loc + hp_minus = () -> + for _, char in pairs(chars_at_loc) + char\set_field("hp",char.data.hp - 3) + if world.server + hp_minus! + if world.client + ui = ui or require "ui" + ui.tween_hit(char,1, hp_minus) + +mod.Tumble = Tumble + +mod diff --git a/src/ability_reg.moon b/src/ability_reg.moon new file mode 100644 index 0000000..4724043 --- /dev/null +++ b/src/ability_reg.moon @@ -0,0 +1,43 @@ +ui = require "ui" +reg = ... + +reg.list = {} + +-- name - string, the name of the ability +-- infocard, the table of info and description to draw when looking at the ability +-- data, the table that contains data and functions for running the ability +-- visuals, the table of sprites that the data needs to function +class Ability + @children = {} + new: (name, data) => + @sprite = @@sprite + @name = name or @.__class.__name + @data = data + + @__inherited: (child) => + assert(child.use, "abilities must have a .use") + assert(type(child.use) == "function", "abilities must have a .use() function") + assert(type(child.load) == "function", "abilities must have a .load() that shows their infocard") + assert(type(child.unload) == "function", "abilities must have an .unload() that removes their infocard") + assert(child.text, "ability must have text") + assert(child.description, "ability must have a description") + assert(child.hits_icon, "ability must have a hits icon") + assert(child.speed, "ability must have a speed") + @@.children[child.__name] = child + reg[child.__name] = child + + load: () => + main = require "main" + infocard = am.group!\tag("infocard") + main.root("screen")\append(infocard) + ui.build_infocard(infocard,@@) + + unload: () => + main = require "main" + main.root\remove("infocard") + --__tostring: () => + -- return string.format("<%s, %s>",@.__class.__name, @name or ".name missing") + +reg["Ability"] = Ability + +return reg diff --git a/src/action.moon b/src/action.moon new file mode 100644 index 0000000..e9b1150 --- /dev/null +++ b/src/action.moon @@ -0,0 +1,58 @@ +-- Actions to send to the server, and to receive +char = require "char" +mod = ... + +mod.msg = { + "request_class_change" ,--client to server request + "confirm_class_change" ,-- server to client answer + "deny_class_change" ,-- server to client answer + "info_class_change" ,-- server to client info + "player_joined" ,--client to server hello + "info_player_joined" ,-- server to client info + "info_player_dropped" ,-- server to client info + "request_campaign_start" , --client to server, start game + "info_campaign_start" , --server to client, game is starting + "request_player_list" ,-- client to server request + "respond_player_list" ,--server to client respond with player list + "info_room" ,-- server to client notify about room info + "info_turnup", -- sever to client, when is the turn up? + "info_enemy_party", --server to client notify about enemy party + "set_action" ,-- client to server, notify action for next turn + "info_timeref" , --server to client, when the next turn is up + "info_actions" ,-- server to client set animations to play + "info_deaths", -- server to client, deaths that are about to happen + "info_loot" ,--server to client loot for clearing a room + "request_camp_action" ,--client to server what action to take at camp + "info_camp" ,--server to client actions taken at camp + "info_defeat", --server to client, you're done! +} +mod.msg_rev = {} +for k,v in pairs(mod.msg) do + mod.msg_rev[v] = k + +mod.action_reg = {} +mod.class_counter = 1 + +mod.request_class_change = (what) -> + assert(what, "class may not be nil") + assert(char.class_order_rev[what], "class must be one of " .. tostring(char.class_order)) + msg_json = am.to_json({msg:"request_class_change", time:am.current_time(), class:what}) + am.eval_js(string.format("CLIENT.send(%q);",msg_json)) + +mod.start_game = () -> + msg_json = am.to_json({msg:"request_campaign_start", time:am.current_time()}) + am.eval_js(string.format("CLIENT.send(%q);",msg_json)) + +mod.sync_players = () -> + msg_json = am.to_json({msg:"request_player_list", time:am.current_time()}) + am.eval_js(string.format("CLIENT.send(%q);",msg_json)) + +mod.set_action = (name) -> + msg_json = am.to_json({ + msg: "set_action" + time: am.current_time! + action: name + }) + am.eval_js(string.format("CLIENT.send(%q);",msg_json)) + +mod diff --git a/src/battle_menu.moon b/src/battle_menu.moon new file mode 100644 index 0000000..84b8aa9 --- /dev/null +++ b/src/battle_menu.moon @@ -0,0 +1,129 @@ +world = require "world" +main = require "main" +color = require "color" +ui = require "ui" +bp = require "broadphase" +pass = require "a_pass" +import Pass from pass +action = require "action" + +mod = ... + +pip_width = 24 + +mod.time_pips = am.group! +pip_x_start = ((main.pips/2)+1) * -pip_width +pip_sprites = {} +ms_per_pip = 1000 + +mod.ability_selector = am.group! +mod.playerturn_up = 0 + +mod.set_playerturn_up = (time) => + mod.playerturn_up = time + +mod.victory_g = am.group! +mod.victory_text = {} + +mod.action_phase = am.group! --group used to animate characters during battle, also holds dammage values +mod.victory_show = false +mod.loaded_ability = nil + +mod.load = () -> + pip_trans = am.translate(pip_x_start,240 - (pip_width/2)) + for i = 1,main.pips + pip_loc = am.translate(pip_width*i,0) + pip_trans\append(pip_loc^ am.sprite("data/pip_frame.png",color.white)) + light_sprite = am.sprite("data/pip_dark.png",color.white) + pip_loc\append(light_sprite) + table.insert(pip_sprites,light_sprite) + mod.time_pips\append(pip_trans) + ability_buttons = {} + ability_trans = am.group! + main.root("screen")\append(mod.victory_g) + for k,v in pairs({"V","I","C","T","O","R","Y"}) + n = am.scale(2)^am.translate((k-3.5)*20,0)^am.text(v,color.fg) + n\action(coroutine.create(() -> + while true + for k,v in pairs(mod.victory_text) + v.hidden = not mod.victory + coroutine.yield! + )) + n\action(am.series({ + am.delay(k*0.25), + am.loop(() -> am.series({ + am.tween(n("translate"),1,{y:30},am.ease.sine), + am.tween(n("translate"),1,{y:0},am.ease.sine) + })) + })) + table.insert(mod.victory_text,n) + mod.victory_g\append(n) + mod.ability_selector\append(ability_trans) + for i = 1,4 + trans_x = (-150 * (i-2))+32 + trans_y = 200 + --ui.create_button needs to be relative to global for hitboxes to work right + ability_slot_button = ui.create_any_button(ability_trans,3,3,trans_x,trans_y) + lpd = main.world.localplayer.data + print("lpd was:",lpd) + ability = lpd.abilities[i] or Pass + print("Ability was",ability.__name) + print("Sprite was",ability and ability.sprite) + ability_slot_icon = am.sprite(ability and ability.sprite or "data/no_action.png", color.white, "left","top")\tag("icon") + ability_slot_button.name = ability.__name + ability_slot_button.ability = ability + ability_slot_button.node\append(am.translate(trans_x + 16,trans_y - 16)^ ability_slot_icon) + table.insert(ability_buttons, ability_slot_button) + print("test") + main.root("screen")\append(mod.time_pips) + main.root("screen")\append(mod.ability_selector) + main.root("screen")\append(mod.action_phase) + mod.time_pips\action(coroutine.create(() -> + print("Pips in action:",pip_sprites) + while true + if not mod.victory + currtime = am.eval_js("new Date().getTime()") + elapsed_time = currtime - main.world.time_ref + npips = math.min(math.floor(elapsed_time/ms_per_pip),main.pips) + for i = 1,npips + pip_sprites[i].source = "data/pip_light.png" + for i = npips+2,main.pips + if i > 0 and i <= main.pips + pip_sprites[i].source = "data/pip_dark.png" + coroutine.yield! + )) + touch_indicator = am.group! + touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") + touch_loc = am.translate(0,0)^ touch_cursor + touch_indicator\append(touch_loc) + mod.ability_selector\append(touch_indicator) + mod.ability_selector\action(coroutine.create(() -> + while true + if #main.win\active_touches! > 0 + touch = main.win\touch_position(1) + touch_cursor.color = vec4(1,1,1,1) + touch_loc.x = touch.x + touch_loc.y = touch.y + col_but = bp.check(touch.x, touch.y + 96) + if #col_but > 0 + touch_cursor.color = vec4(0.5,0.5,0.5,0.5) + if main.win\touch_began(1) + print("Checking ability buttons:",ability_buttons, "against col but:",col_but) + for _,button in ipairs(ability_buttons) + if button == col_but[1] + print("Found button") + action.set_action(button.name) + if mod.loaded_ability + mod.loaded_ability\unload! + button.ability\load! + mod.loaded_ability = ability + coroutine.yield! + )) + + +mod.unload = () -> + main.root("screen")\remove(mod.time_pips) + main.root("screen")\remove(mod.ability_selector) + main.root("screen")\remove(mod.action_phase) + +mod diff --git a/src/broadphase.moon b/src/broadphase.moon new file mode 100644 index 0000000..540680e --- /dev/null +++ b/src/broadphase.moon @@ -0,0 +1,69 @@ + +--[[I was going to write my own physics engine, but decided to just use a library]] + +--[[Keeps track of the collisions in the world]] +mod = ... +util = require "util" +bump = require "bump" +main = require "main" --for debugging +import Vec3 from util + +mod.world = bump.newWorld(8) +--passes the bottom left x,y and the width,height of the object +mod.add = (ref,x,y,width,height) -> + print("Making phys for ref:",ref) + --print("At the time of adding, main.screenpos is",main.screenpos, "x:", x, "y:",y) + --print("Width:",width, "height", height) + --print("ref.node is", ref.node) + graphic_x = 0--x/4 + graphic_y = 0--y --+ (height / 2) + --print("graphic_y:",graphic_y) + physx = x + physy = y + --lx = x - main.screenpos.x - (width / 2) + --ly = y - main.screenpos.y - (height / 2) + lx = x --+ (width / 2) + ly = y --+ (height / 2) + --tn = am.translate(lx,ly)\tag("position") + --node = tn ^ am.rect(0,0,width,height,vec4(0.5,0.5,0.5,0.5))\tag("sprite") + --(main.world("world_platforms"))\append(node) --for debugging + --print("ref node:",ref.node) + --print("positionnode:",(ref.node)("position")) + --print("ref's children:") + --for k,v in ref.node\child_pairs() + --print(k,v) + --[[The debug display for the physics box]] + positionnode = ref.node + --positionnode\append(am.rect(-width/2,-height/2, width/2,height/2,vec4(1.0,0.0,0.0,1.0))\tag("dsprite")) + --positionnode\append(am.rect(x , y , x + width, y - height,vec4(1.0,0.0,0.0,0.5))\tag("dsprite")) + --positionnode\append(am.rect(lx,ly,lx + width,ly + height,vec4(0.5,0.5,0.5,0.5))\tag("dsprite")) + --[[The actual physics object for bump.lua]] + mod.world\add(ref,physx,physy,width,height) + --mod.world\add(ref,x - (width/2),y,width,height) + print("physics node:",node) + node + +mod.update = (ref,x,y,newwidth,newheight) -> + mod.world\update(ref,x,y,newwidth,newheight) + +mod.move = (ref,x,y,filter) -> + mod.world\move(ref,x,y,filter) + +mod.remove = (ref) -> + mod.world\remove(ref) + +mod.check = (x,y) -> + mod.world\queryPoint(x,y) + + +class PhysGroup + new: (offset, size, extra = {}) => + @offset = offset + @size = size + @extra = extra + +mod.gravity = 0.5 +mod.term_fall_vel = 10 +mod.PhysGroup = PhysGroup +mod + diff --git a/src/bump.lua b/src/bump.lua new file mode 100644 index 0000000..6dabca7 --- /dev/null +++ b/src/bump.lua @@ -0,0 +1,773 @@ +local bump = { + _VERSION = 'bump v3.1.7', + _URL = 'https://github.com/kikito/bump.lua', + _DESCRIPTION = 'A collision detection library for Lua', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2014 Enrique GarcÃa Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +------------------------------------------ +-- Auxiliary functions +------------------------------------------ +local DELTA = 1e-10 -- floating-point margin of error + +local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max + +local function sign(x) + if x > 0 then return 1 end + if x == 0 then return 0 end + return -1 +end + +local function nearest(x, a, b) + if abs(a - x) < abs(b - x) then return a else return b end +end + +local function assertType(desiredType, value, name) + if type(value) ~= desiredType then + error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')') + end +end + +local function assertIsPositiveNumber(value, name) + if type(value) ~= 'number' or value <= 0 then + error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')') + end +end + +local function assertIsRect(x,y,w,h) + assertType('number', x, 'x') + assertType('number', y, 'y') + assertIsPositiveNumber(w, 'w') + assertIsPositiveNumber(h, 'h') +end + +local defaultFilter = function() + return 'slide' +end + +------------------------------------------ +-- Rectangle functions +------------------------------------------ + +local function rect_getNearestCorner(x,y,w,h, px, py) + return nearest(px, x, x+w), nearest(py, y, y+h) +end + +-- This is a generalized implementation of the liang-barsky algorithm, which also returns +-- the normals of the sides where the segment intersects. +-- Returns nil if the segment never touches the rect +-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge +local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2) + ti1, ti2 = ti1 or 0, ti2 or 1 + local dx, dy = x2-x1, y2-y1 + local nx, ny + local nx1, ny1, nx2, ny2 = 0,0,0,0 + local p, q, r + + for side = 1,4 do + if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left + elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right + elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top + else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom + end + + if p == 0 then + if q <= 0 then return nil end + else + r = q / p + if p < 0 then + if r > ti2 then return nil + elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny + end + else -- p > 0 + if r < ti1 then return nil + elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny + end + end + end + end + + return ti1,ti2, nx1,ny1, nx2,ny2 +end + +-- Calculates the minkowsky difference between 2 rects, which is another rect +local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2) + return x2 - x1 - w1, + y2 - y1 - h1, + w1 + w2, + h1 + h2 +end + +local function rect_containsPoint(x,y,w,h, px,py) + return px - x > DELTA and py - y > DELTA and + x + w - px > DELTA and y + h - py > DELTA +end + +local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2) + return x1 < x2+w2 and x2 < x1+w1 and + y1 < y2+h2 and y2 < y1+h1 +end + +local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2) + local dx = x1 - x2 + (w1 - w2)/2 + local dy = y1 - y2 + (h1 - h2)/2 + return dx*dx + dy*dy +end + +local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY) + goalX = goalX or x1 + goalY = goalY or y1 + + local dx, dy = goalX - x1, goalY - y1 + local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2) + + local overlaps, ti, nx, ny + + if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other + local px, py = rect_getNearestCorner(x,y,w,h, 0, 0) + local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection + ti = -wi * hi -- ti is the negative area of intersection + overlaps = true + else + local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge) + + -- item tunnels into other + if ti1 + and ti1 < 1 + and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner + and (0 < ti1 + DELTA + or 0 == ti1 and ti2 > 0) + then + ti, nx, ny = ti1, nx1, ny1 + overlaps = false + end + end + + if not ti then return end + + local tx, ty + + if overlaps then + if dx == 0 and dy == 0 then + -- intersecting and not moving - use minimum displacement vector + local px, py = rect_getNearestCorner(x,y,w,h, 0,0) + if abs(px) < abs(py) then py = 0 else px = 0 end + nx, ny = sign(px), sign(py) + tx, ty = x1 + px, y1 + py + else + -- intersecting and moving - move in the opposite direction + local ti1, _ + ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1) + if not ti1 then return end + tx, ty = x1 + dx * ti1, y1 + dy * ti1 + end + else -- tunnel + tx, ty = x1 + dx * ti, y1 + dy * ti + end + + return { + overlaps = overlaps, + ti = ti, + move = {x = dx, y = dy}, + normal = {x = nx, y = ny}, + touch = {x = tx, y = ty}, + itemRect = {x = x1, y = y1, w = w1, h = h1}, + otherRect = {x = x2, y = y2, w = w2, h = h2} + } +end + +------------------------------------------ +-- Grid functions +------------------------------------------ + +local function grid_toWorld(cellSize, cx, cy) + return (cx - 1)*cellSize, (cy-1)*cellSize +end + +local function grid_toCell(cellSize, x, y) + return floor(x / cellSize) + 1, floor(y / cellSize) + 1 +end + +-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing", +-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf +-- It has been modified to include both cells when the ray "touches a grid corner", +-- and with a different exit condition + +local function grid_traverse_initStep(cellSize, ct, t1, t2) + local v = t2 - t1 + if v > 0 then + return 1, cellSize / v, ((ct + v) * cellSize - t1) / v + elseif v < 0 then + return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v + else + return 0, math.huge, math.huge + end +end + +local function grid_traverse(cellSize, x1,y1,x2,y2, f) + local cx1,cy1 = grid_toCell(cellSize, x1,y1) + local cx2,cy2 = grid_toCell(cellSize, x2,y2) + local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2) + local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2) + local cx,cy = cx1,cy1 + + f(cx, cy) + + -- The default implementation had an infinite loop problem when + -- approaching the last cell in some occassions. We finish iterating + -- when we are *next* to the last cell + while abs(cx - cx2) + abs(cy - cy2) > 1 do + if tx < ty then + tx, cx = tx + dx, cx + stepX + f(cx, cy) + else + -- Addition: include both cells when going through corners + if tx == ty then f(cx + stepX, cy) end + ty, cy = ty + dy, cy + stepY + f(cx, cy) + end + end + + -- If we have not arrived to the last cell, use it + if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end + +end + +local function grid_toCellRect(cellSize, x,y,w,h) + local cx,cy = grid_toCell(cellSize, x, y) + local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize) + return cx, cy, cr - cx + 1, cb - cy + 1 +end + +------------------------------------------ +-- Responses +------------------------------------------ + +local touch = function(world, col, x,y,w,h, goalX, goalY, filter) + return col.touch.x, col.touch.y, {}, 0 +end + +local cross = function(world, col, x,y,w,h, goalX, goalY, filter) + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) + return goalX, goalY, cols, len +end + +local slide = function(world, col, x,y,w,h, goalX, goalY, filter) + goalX = goalX or x + goalY = goalY or y + + local tch, move = col.touch, col.move + if move.x ~= 0 or move.y ~= 0 then + if col.normal.x ~= 0 then + goalX = tch.x + else + goalY = tch.y + end + end + + col.slide = {x = goalX, y = goalY} + + x,y = tch.x, tch.y + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) + return goalX, goalY, cols, len +end + +local bounce = function(world, col, x,y,w,h, goalX, goalY, filter) + goalX = goalX or x + goalY = goalY or y + + local tch, move = col.touch, col.move + local tx, ty = tch.x, tch.y + + local bx, by = tx, ty + + if move.x ~= 0 or move.y ~= 0 then + local bnx, bny = goalX - tx, goalY - ty + if col.normal.x == 0 then bny = -bny else bnx = -bnx end + bx, by = tx + bnx, ty + bny + end + + col.bounce = {x = bx, y = by} + x,y = tch.x, tch.y + goalX, goalY = bx, by + + local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter) + return goalX, goalY, cols, len +end + +------------------------------------------ +-- World +------------------------------------------ + +local World = {} +local World_mt = {__index = World} + +-- Private functions and methods + +local function sortByWeight(a,b) return a.weight < b.weight end + +local function sortByTiAndDistance(a,b) + if a.ti == b.ti then + local ir, ar, br = a.itemRect, a.otherRect, b.otherRect + local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h) + local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h) + return ad < bd + end + return a.ti < b.ti +end + +local function addItemToCell(self, item, cx, cy) + self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'}) + local row = self.rows[cy] + row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})} + local cell = row[cx] + self.nonEmptyCells[cell] = true + if not cell.items[item] then + cell.items[item] = true + cell.itemCount = cell.itemCount + 1 + end +end + +local function removeItemFromCell(self, item, cx, cy) + local row = self.rows[cy] + if not row or not row[cx] or not row[cx].items[item] then return false end + + local cell = row[cx] + cell.items[item] = nil + cell.itemCount = cell.itemCount - 1 + if cell.itemCount == 0 then + self.nonEmptyCells[cell] = nil + end + return true +end + +local function getDictItemsInCellRect(self, cl,ct,cw,ch) + local items_dict = {} + for cy=ct,ct+ch-1 do + local row = self.rows[cy] + if row then + for cx=cl,cl+cw-1 do + local cell = row[cx] + if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling + for item,_ in pairs(cell.items) do + items_dict[item] = true + end + end + end + end + end + + return items_dict +end + +local function getCellsTouchedBySegment(self, x1,y1,x2,y2) + + local cells, cellsLen, visited = {}, 0, {} + + grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy) + local row = self.rows[cy] + if not row then return end + local cell = row[cx] + if not cell or visited[cell] then return end + + visited[cell] = true + cellsLen = cellsLen + 1 + cells[cellsLen] = cell + end) + + return cells, cellsLen +end + +local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter) + local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2) + local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1 + local visited, itemInfo, itemInfoLen = {},{},0 + for i=1,len do + cell = cells[i] + for item in pairs(cell.items) do + if not visited[item] then + visited[item] = true + if (not filter or filter(item)) then + rect = self.rects[item] + l,t,w,h = rect.x,rect.y,rect.w,rect.h + + ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1) + if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then + -- the sorting is according to the t of an infinite line, not the segment + tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge) + itemInfoLen = itemInfoLen + 1 + itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)} + end + end + end + end + end + table.sort(itemInfo, sortByWeight) + return itemInfo, itemInfoLen +end + +local function getResponseByName(self, name) + local response = self.responses[name] + if not response then + error(('Unknown collision type: %s (%s)'):format(name, type(name))) + end + return response +end + + +-- Misc Public Methods + +function World:addResponse(name, response) + self.responses[name] = response +end + +function World:project(item, x,y,w,h, goalX, goalY, filter) + assertIsRect(x,y,w,h) + + goalX = goalX or x + goalY = goalY or y + filter = filter or defaultFilter + + local collisions, len = {}, 0 + + local visited = {} + if item ~= nil then visited[item] = true end + + -- This could probably be done with less cells using a polygon raster over the cells instead of a + -- bounding rect of the whole movement. Conditional to building a queryPolygon method + local tl, tt = min(goalX, x), min(goalY, y) + local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h) + local tw, th = tr-tl, tb-tt + + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th) + + local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch) + + for other,_ in pairs(dictItemsInCellRect) do + if not visited[other] then + visited[other] = true + + local responseName = filter(item, other) + if responseName then + local ox,oy,ow,oh = self:getRect(other) + local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY) + + if col then + col.other = other + col.item = item + col.type = responseName + + len = len + 1 + collisions[len] = col + end + end + end + end + + table.sort(collisions, sortByTiAndDistance) + + return collisions, len +end + +function World:countCells() + local count = 0 + for _,row in pairs(self.rows) do + for _,_ in pairs(row) do + count = count + 1 + end + end + return count +end + +function World:hasItem(item) + return not not self.rects[item] +end + +function World:getItems() + local items, len = {}, 0 + for item,_ in pairs(self.rects) do + len = len + 1 + items[len] = item + end + return items, len +end + +function World:countItems() + local len = 0 + for _ in pairs(self.rects) do len = len + 1 end + return len +end + +function World:getRect(item) + local rect = self.rects[item] + if not rect then + error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.') + end + return rect.x, rect.y, rect.w, rect.h +end + +function World:toWorld(cx, cy) + return grid_toWorld(self.cellSize, cx, cy) +end + +function World:toCell(x,y) + return grid_toCell(self.cellSize, x, y) +end + + +--- Query methods + +function World:queryRect(x,y,w,h, filter) + + assertIsRect(x,y,w,h) + + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) + local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch) + + local items, len = {}, 0 + + local rect + for item,_ in pairs(dictItemsInCellRect) do + rect = self.rects[item] + if (not filter or filter(item)) + and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h) + then + len = len + 1 + items[len] = item + end + end + + return items, len +end + +function World:queryPoint(x,y, filter) + local cx,cy = self:toCell(x,y) + local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1) + + local items, len = {}, 0 + + local rect + for item,_ in pairs(dictItemsInCellRect) do + rect = self.rects[item] + if (not filter or filter(item)) + and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y) + then + len = len + 1 + items[len] = item + end + end + + return items, len +end + +function World:querySegment(x1, y1, x2, y2, filter) + local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) + local items = {} + for i=1, len do + items[i] = itemInfo[i].item + end + return items, len +end + +function World:querySegmentWithCoords(x1, y1, x2, y2, filter) + local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter) + local dx, dy = x2-x1, y2-y1 + local info, ti1, ti2 + for i=1, len do + info = itemInfo[i] + ti1 = info.ti1 + ti2 = info.ti2 + + info.weight = nil + info.x1 = x1 + dx * ti1 + info.y1 = y1 + dy * ti1 + info.x2 = x1 + dx * ti2 + info.y2 = y1 + dy * ti2 + end + return itemInfo, len +end + + +--- Main methods + +function World:add(item, x,y,w,h) + local rect = self.rects[item] + if rect then + error('Item ' .. tostring(item) .. ' added to the world twice.') + end + assertIsRect(x,y,w,h) + + self.rects[item] = {x=x,y=y,w=w,h=h} + + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) + for cy = ct, ct+ch-1 do + for cx = cl, cl+cw-1 do + addItemToCell(self, item, cx, cy) + end + end + + return item +end + +function World:remove(item) + local x,y,w,h = self:getRect(item) + + self.rects[item] = nil + local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h) + for cy = ct, ct+ch-1 do + for cx = cl, cl+cw-1 do + removeItemFromCell(self, item, cx, cy) + end + end +end + +function World:update(item, x2,y2,w2,h2) + local x1,y1,w1,h1 = self:getRect(item) + w2,h2 = w2 or w1, h2 or h1 + assertIsRect(x2,y2,w2,h2) + + if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then + + local cellSize = self.cellSize + local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1) + local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2) + + if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then + + local cr1, cb1 = cl1+cw1-1, ct1+ch1-1 + local cr2, cb2 = cl2+cw2-1, ct2+ch2-1 + local cyOut + + for cy = ct1, cb1 do + cyOut = cy < ct2 or cy > cb2 + for cx = cl1, cr1 do + if cyOut or cx < cl2 or cx > cr2 then + removeItemFromCell(self, item, cx, cy) + end + end + end + + for cy = ct2, cb2 do + cyOut = cy < ct1 or cy > cb1 + for cx = cl2, cr2 do + if cyOut or cx < cl1 or cx > cr1 then + addItemToCell(self, item, cx, cy) + end + end + end + + end + + local rect = self.rects[item] + rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2 + + end +end + +function World:move(item, goalX, goalY, filter) + local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter) + + self:update(item, actualX, actualY) + + return actualX, actualY, cols, len +end + +function World:check(item, goalX, goalY, filter) + filter = filter or defaultFilter + + local visited = {[item] = true} + local visitedFilter = function(itm, other) + if visited[other] then return false end + return filter(itm, other) + end + + local cols, len = {}, 0 + + local x,y,w,h = self:getRect(item) + + local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter) + + while projected_len > 0 do + local col = projected_cols[1] + len = len + 1 + cols[len] = col + + visited[col.other] = true + + local response = getResponseByName(self, col.type) + + goalX, goalY, projected_cols, projected_len = response( + self, + col, + x, y, w, h, + goalX, goalY, + visitedFilter + ) + end + + return goalX, goalY, cols, len +end + + +-- Public library functions + +bump.newWorld = function(cellSize) + cellSize = cellSize or 64 + assertIsPositiveNumber(cellSize, 'cellSize') + local world = setmetatable({ + cellSize = cellSize, + rects = {}, + rows = {}, + nonEmptyCells = {}, + responses = {} + }, World_mt) + + world:addResponse('touch', touch) + world:addResponse('cross', cross) + world:addResponse('slide', slide) + world:addResponse('bounce', bounce) + + return world +end + +bump.rect = { + getNearestCorner = rect_getNearestCorner, + getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices, + getDiff = rect_getDiff, + containsPoint = rect_containsPoint, + isIntersecting = rect_isIntersecting, + getSquareDistance = rect_getSquareDistance, + detectCollision = rect_detectCollision +} + +bump.responses = { + touch = touch, + cross = cross, + slide = slide, + bounce = bounce +} + +return bump diff --git a/src/char.moon b/src/char.moon new file mode 100644 index 0000000..5e1a35c --- /dev/null +++ b/src/char.moon @@ -0,0 +1,307 @@ + +util = require "util" +broadphase = require "broadphase" +main = require "main" +constrain = require "constrain" +color = require "color" +room = require "room" +import LobbyRoom from room +ability_reg = require "ability_reg" + + +mod = ... +mod.characters = {} +mod.classes = {} +require "char_tank" +require "char_mage" +require "char_theif" +require "char_fool" +require "char_jugg" +mod.class_order = { + "Tumbler", + "Fire Breather", + "Juggler", + "Troubador", + "Juggernaut" +} +mod.class_order_rev = {} +for k,v in ipairs(mod.class_order) + mod.class_order_rev[v] = k +print("After requireing characters, mod.classes was",mod.classes) +mod.enemies = {} +--require "e_rat" +require "e_bethany" +require "e_ruminating_randy" +require "e_mopey_marvin" +require "e_sullen_salley" +require "e_child" +print("After requireing rat, mod.enemies was",mod.enemies) +--print("test") +--[[Stores a single input, and the time it was inputed]] +class KeyInput + new:(key = "<input>") => + @time = 0 + @key = key + @value = false + +--print "test" + +class AnimFrame + new:(anim,interupt,mode) => + @anim = anim + @interupt = interupt + @mode = mode + +class ActionInput + new:(action = "<input>") => + @time = 0 + @action = action + +mod.sprite_direction_co = () => + return () -> + while true + @stop_anim(@sprites.right) + @stop_anim(@sprites.left) + @stop_anim(@sprites.stop_right) + @stop_anim(@sprites.stop_left) + @stop_anim(@sprites.falling_left) + @stop_anim(@sprites.falling_right) + for input in *@inputs + if @velocity.x > 0.01 + @set_anim(@sprites.right,1,"loop") + break + elseif @velocity.x < -0.01 + @set_anim(@sprites.left,1,"loop") + break + coroutine.yield! + +mod.can_die_co = () => + return () -> + while not @dead + coroutine.yield! + + for k,v in pairs @sprites + if k ~= "idle" + @stop_anim(v) + if @sprites.die + @set_anim(@sprites.die,5) + coroutine.yield(true) + +mod.make_animate = (c) -> + assertf(c.sprites ~= nil, "Tried to animate something that had no .sprites: %q", c.__class.__name) + assertf(c.sprites.idle ~= nil and #c.sprites.idle > 0, "Tried to animate something without a .idle animation: %q", c.__class.__name) + c.anim_stack = {AnimFrame(c.sprites.idle,0,"loop")} + c.anim = c.sprites.idle + c.keyframe = 0 + c.animrate = c.animrate or 1 + c.anim_interupt = 0 + c.set_anim = (self,tbl,interupt,mode) -> + if type(tbl) ~= "table" + error("Tried to set anim to something that was not a table!",2) + if #tbl == 0 + error("Tried to set anim to an empty table",2) + if interupt > @anim_interupt + table.insert(@anim_stack,AnimFrame(tbl,interupt,mode)) + @anim = @anim_stack[#@anim_stack].anim + @anim_interupt = interupt + + c.stop_anim = (self,tbl,err_if_unable=false) -> + anim_found = false + for k,v in pairs @anim_stack + if v.anim == tbl + --print("Found anim to remove") + anim_found = true + table.remove(@anim_stack,k) + break + if err_if_unable + assertf(anim_found, "Could not find animation to remove") + @anim = @anim_stack[#@anim_stack].anim + @anim_interupt = @anim_stack[#@anim_stack].interupt + + c.node\action(coroutine.create(() -> + while not c.dead + c.keyframe = math.floor(am.current_time()*c.animrate) % #c.anim + spritename = c.anim[c.keyframe + 1] + assert(spritename, "Failed to find an appropriate image to draw.") + --print("Setting:",spritename) + c.node("sprite").source = spritename + coroutine.yield! + --we are dead + keyframe_0 = am.current_time() + while c.keyframe < #c.anim - 1 + c.keyframe = math.floor((am.current_time! - keyframe_0)*c.animrate) + assert(c.anim[c.keyframe + 1], "Failed to find an appropriate image to draw.") + c.node("sprite").source = c.anim[c.keyframe + 1] + coroutine.yield! + c\remove() + coroutine.yield(true) + )) + +mod.inherited = {} +hp_bar_width = 20 +--[[The character, extended to make both players and ai]] +class Character + @classes = {} + @players = {} -- players singleton, [peerid] = Character + new:(uname, data, charclass) => + assert(charclass, "Charclass may not be nil") + @uname = uname or false + @data = data or { + status: "active", + location: -1, + position: charclass.default_position + } + @class = charclass + assert(@class.name, "Character classes must have a name") + @calc_class_values! + @node = am.group! + + --print("Creating character!",uname,data,charclass) + @node\append(am.translate(0,0)\tag("char_translate")^ am.sprite(@class.sprite,color.white,"center","bottom")\tag("char_sprite")) + print("A character has been created!") + print(debug.traceback!) + --Draw healthbar + healthbar_trans = am.translate(0,60) + healthbar_left = am.sprite("data/bar_left.png", color.white,"left","center")\tag("hp_l") + healthbar_right = am.sprite("data/bar_right.png", color.white, "right","center")\tag("hp_r") + healthbar_bar = am.sprite("data/bar_mid.png", color.white,"left","center")\tag("hp_b") + healthbar_fill = am.sprite("data/bar_fill.png", color.white,"left","center")\tag("hp_f") + healthbar_scale = am.scale(hp_bar_width,1) + healthbar_fill_scale = am.scale(hp_bar_width + 1,1)\tag("hp_fill_scale") + healthbar_trans\append(am.translate(-hp_bar_width,0)^ healthbar_left) + healthbar_trans\append(am.translate(hp_bar_width,0)^ healthbar_right) + healthbar_trans\append(am.translate(-hp_bar_width/2,0)^ healthbar_scale^ healthbar_bar) + healthbar_trans\append(am.translate((-hp_bar_width/2) - 1,0)^ healthbar_fill_scale^ healthbar_fill) + @.node("char_sprite")\append(healthbar_trans) + --End draw healthbar + assert(@.__class.draw,"Characters must have a draw() method") + table.insert(mod.characters,@) + constrain(@,"set anim", (self,value) -> + assertf(type(value) == "table", "Tried to set anim on %q to something other than a table (%q)",@, type(value)) + assert(#value > 0, "Tried to set animation for char to something with 0 frames!") + ) + assert(@.__class != Character,"Character class must be subclassed") + --main.root("world_characters")\append(@node) + + __tostring: () => + return string.format( + "<%s, %s> at (%d)", + @.__class.__name, + (@class and @class.name or "no class"), + ( @data and @data.position or -1) + ) + + @__inherited: (c) => + assert(c, "Inheritance must exist") + assert(c.__name, "Inherited class must have a .__name") + @@.classes[c.__name] = c + mod.inherited[c.__name] = c + + calc_class_values: () => + for k,v in pairs(@class) + if k\match("^default") + field = k\match("^default_(.*)") + if type(v) == "function" + @data[field] = v! + else + @data[field] = v + + set_field: (name, value) => + assert(@data[name], "Field must exist to be set") + @data[name] = value + print("my data table is:",@data) + if name == "hp" + perc = (@data.hp / @data.maxhp) * 20 + @.node("hp_fill_scale").x = perc + + serialize: () => + print("Serializing char:",@) + print("Name:", @@__name) + print("uname:",@uname) + print("data:",@data) + data_abilities = {} + for i,ability in pairs(@data.abilities) + data_abilities[i] = ability.__name + data_copy = table.shallow_copy(@data) + data_copy.abilities = data_abilities + print("class.name:",@class.name) + ret = am.to_json({name:@@__name, uname:@uname, data:data_copy, class:@class.name}) + print("Ret is:",ret) + ret + + deserialize: (data) -> + print("Deserializing character") + tbl = am.parse_json(data) + if mod.classes[tbl.class] + data_abilities = {} + for i, ability_name in pairs(tbl.data.abilities) + data_abilities[i] = ability_reg[ability_name] + tbl.data.abilities = data_abilities + return mod.inherited[tbl.name](tbl.uname, tbl.data, mod.classes[tbl.class]) + elseif mod.enemies[tbl.class] + e = mod.Enemy(tbl.data, mod.enemies[tbl.class]) + e.uname = tbl.uname + return e + + + draw:(screenpos) => + print("draw") + remove: () => + @node\remove_all! + die: () => + @dead = true + print(@,"is dieing, node is",@.node,"and color is",color) + @.node("char_sprite")\append(am.line(vec2(-10,-10),vec2(10,10),5,color.bright)) + @.node("char_sprite")\append(am.line(vec2(10,-10),vec2(-10,10),5,color.bright)) + enter_room: (room) => + @room = room + print("Character",@,"entered room",room) + @.node("char_translate").y = room.floor_y + if room.__class == LobbyRoom + print("Class was lobbyRoom") + rng_x = math.random(-(main.width/2), (main.width/2)) + print("RNG x:",rng_x) + @.node("char_translate").x = rng_x + else + print("Class was not LobbyRoom") + x_pos = @room\player_location_of(@data.position) + print("Got x pos for",@,x_pos) + @.node("char_translate").x = x_pos + math.random(-15,15) --some random variance to stop stacking + set_class: (newclass) => + assert(newclass, "Cannot set a class to nil") + @class = newclass + @calc_class_values! + @.node("char_sprite").source = newclass.sprite + + set_position: (pos) => + @data.position = pos + + set_location: (loc) => + @data.location = loc + +mod.enemy_counter = 0 +class Enemy extends Character + new: (data, eclass) => + super(eclass.name .. ":" .. tostring(mod.enemy_counter), data, eclass) + mod.enemy_counter += 1 + nwuname: (uname, data, eclass) -> + @(data, eclass) + + mod.enemy_counter += 1 + __tostring: () => + return string.format("<%s> at (%d)",@uname, @data.position or 0) + + enter_room: (room) => --overload character's to get enemy locations + print("Character",@,"entered room",room) + @room = room + @.node("char_translate").y = @room.floor_y + @.node("char_translate").x = @room\enemy_location_of(@data.position) + + select_action: () => + return @class.select_action(@) + +mod["Character"] = Character +mod["KeyInput"] = KeyInput +mod["Enemy"] = Enemy +--Things that extend the character class +mod diff --git a/src/char_fool.moon b/src/char_fool.moon new file mode 100644 index 0000000..d468e3b --- /dev/null +++ b/src/char_fool.moon @@ -0,0 +1,24 @@ +char = require "char" +reg = require "ability_reg" +require "a_firebreath" + +mod = ... + +mod.char = { + name: "Fire Breather", + default_position: 3, + default_abilities: {reg.FireBreath}, + default_maxhp: 3, + default_hp: 3, + default_maxstamina: 3 + default_stamina: 3 + default_maxmana: 3 + default_mana: 3 + sprite:"data/character_4.png" +} +for k,v in pairs(mod.char) + mod[k] = v + +char.classes[mod.char.name] = mod.char + +mod diff --git a/src/char_jugg.moon b/src/char_jugg.moon new file mode 100644 index 0000000..4adc9bb --- /dev/null +++ b/src/char_jugg.moon @@ -0,0 +1,30 @@ +char = require "char" +reg = require "ability_reg" +require "a_test" + +mod = ... + +mod.char = { + name: "Juggernaut", + default_position:1, + default_abilities: { + reg.Everything + }, + default_maxhp: 100, + default_hp: 100, + default_maxstamina: 3, + default_stamina: 3, + default_maxmana: 1, + default_mana: 1, + sprite:"data/character_1.png" +} + +for k,v in pairs(mod.char) + mod[k] = v + +print("reg:",reg) +assert(mod.char.default_abilities[1], "Tumble not found in reg") +char.classes[mod.char.name] = mod.char +print("After adding Tumbler, char.classes is", char.classes) + +mod diff --git a/src/char_mage.moon b/src/char_mage.moon new file mode 100644 index 0000000..c0ff680 --- /dev/null +++ b/src/char_mage.moon @@ -0,0 +1,26 @@ +char = require "char" +reg = require "ability_reg" +require "a_strum" +require "a_dance" +require "a_drum" + +mod = ... + +mod.char = { + name: "Troubador", + default_position:3, + default_abilities: {reg.Strum, reg.Dance, reg.Drum}, + default_maxhp: 2, + default_hp: 2, + default_maxstamina: 1 + default_stamina: 1 + default_maxmana: 5 + default_mana: 5 + sprite:"data/character_3.png" +} +for k,v in pairs(mod.char) + mod[k] = v + +char.classes[mod.char.name] = mod.char + +mod diff --git a/src/char_tank.moon b/src/char_tank.moon new file mode 100644 index 0000000..6017ac1 --- /dev/null +++ b/src/char_tank.moon @@ -0,0 +1,34 @@ +char = require "char" +reg = require "ability_reg" +require "a_tumble" +require "a_highjump" +require "a_physique" + +mod = ... + +mod.char = { + name: "Tumbler", + default_position:1, + default_abilities: { + reg.Tumble, + reg.HighJump, + reg.Physique + }, + default_maxhp: 5, + default_hp: 5, + default_maxstamina: 3, + default_stamina: 3, + default_maxmana: 1, + default_mana: 1, + sprite:"data/character_1.png" +} + +for k,v in pairs(mod.char) + mod[k] = v + +print("reg:",reg) +assert(mod.char.default_abilities[1], "Tumble not found in reg") +char.classes[mod.char.name] = mod.char +print("After adding Tumbler, char.classes is", char.classes) + +mod diff --git a/src/char_theif.moon b/src/char_theif.moon new file mode 100644 index 0000000..6a074ac --- /dev/null +++ b/src/char_theif.moon @@ -0,0 +1,25 @@ +char = require "char" +reg = require "ability_reg" +require "a_knifeslip" +require "a_hackysacks" + +mod = ... + +mod.char = { + name: "Juggler", + default_position: 2, + default_abilities: {reg.KnifeSlip, reg.Juggle}, + default_maxhp: 3, + default_hp: 3, + default_maxstamina: 5 + default_stamina: 5 + default_maxmana: 1 + default_mana: 1 + sprite:"data/character_2.png" +} +for k,v in pairs(mod.char) + mod[k] = v + +char.classes[mod.char.name] = mod.char + +mod diff --git a/src/color.moon b/src/color.moon new file mode 100644 index 0000000..1f87234 --- /dev/null +++ b/src/color.moon @@ -0,0 +1,13 @@ +mod = ... + +mod.fg = vec4(0.983,0.961,0.937,1) +mod.bg = vec4(0.153,0.153,0.267,1) +mod.highlight = vec4(0.949,0.827,0.671,1) +mod.shadow = vec4(0.286,0.302,0.494,1) +mod.bright = vec4(0.776,0.624,0.647,1) +mod.dark = vec4(0.545,0.427,0.612,1) + +mod.transparent = vec4(0,0,0,0) +mod.white = vec4(1,1,1,1) + +mod diff --git a/src/constants.lua b/src/constants.lua new file mode 100644 index 0000000..b2894bf --- /dev/null +++ b/src/constants.lua @@ -0,0 +1,7 @@ +main = require("main") + +mod = ... + +mod.floor_y = 128 + +mod diff --git a/src/constrain.lua b/src/constrain.lua new file mode 100644 index 0000000..56f3b6a --- /dev/null +++ b/src/constrain.lua @@ -0,0 +1,109 @@ +--[[ +A function that allows adding constraints on tables. +This works by makeing a shallow copy of the table, +and setting __index and __newindex metamethods of the (now empty) table, +that access the appropriate values of the shallow copy after doing checks. + +constrain(tbl, ("get " | "set ") + "field name", function(tbl, value) + --this function is called with the table ("self") and the + --value that is being set or retrived +end) + +This module is just a function that can add constraints on a table. Use +it like this: + + local constrain = require("constrain") + local my_tbl = {} + constrain(my_tbl,"get my_field",function(self,value) + --Here, self is my_tbl, value will be + --"my_field" + assert(value > 0 and value < 20, "my_value must be between 0 and 20") + end) + --From now on, if my_tbl.my_field gets set to anything outside of (0:20), + --an error will be thrown. + +This function should be totally transparent to the outside. +]] + +local constrained_tables = {} + +return function(tbl,trigger,func) + local is_empty = true + for k,v in pairs(tbl) do + is_empty = false + break; + end + local meta = getmetatable(tbl) + + --This table has never had constrain() called on it before, + --make the shallow copy with hooks and stuff + if constrained_tables[tbl] == nil then + --Copy all the variables into a shallow copy + local shallow_copy = {} + for k,v in pairs(tbl) do + shallow_copy[k] = v + tbl[k] = nil + end + + --Set the shallow copy's metatable to the original table's + --metatable + setmetatable(shallow_copy,meta) + + --Set the original table's metatable to the hookable thing + local t_meta = {} + t_meta.get_hooks = {} + t_meta.set_hooks = {} + t_meta.__index = function(self,key) + local ret = shallow_copy[key] + for _,hook in pairs(t_meta.get_hooks[key] or {}) do + hook(self,ret) + end + return ret + end + t_meta.__newindex = function(self,key,value) + for _,hook in pairs(t_meta.set_hooks[key] or {}) do + hook(self,value) + end + shallow_copy[key] = value + end + t_meta.__pairs = function(self) + return pairs(shallow_copy) + end + t_meta.__len = function(self) + return #shallow_copy + end + t_meta.__call = function(self,...) + return shallow_copy(...) + end + t_meta.__mode = meta and meta.__mode or "" + t_meta.__gc = meta and meta.__gc + t_meta.__metatable = meta + + setmetatable(tbl,t_meta) + constrained_tables[tbl] = t_meta + end + + --By this point, tbl is a "constrainable" table. we can just + --add functios to it's get_hooks and set_hooks to do whatever checking we need + --functions added to get_hooks should be + -- function(self,value) ... end + --functions added to set_hooks should be + -- function(self,value) ... end + -- + local getset,field = string.match(trigger,"(get) (.+)") + if not getset then + getset,field = string.match(trigger,"(set) (.+)") + end + --local getset,field = string.match(trigger,"(get|set) (.+)") + assert(getset,"constrain() must be called with \"get\" or \"set\" as the first word in the pattern") + assert(field,"constrain() must specify a field to trigger on") + if getset == "get" then + local gh = constrained_tables[tbl].get_hooks + gh[field] = gh[field] or {} + table.insert(gh[field],func) + elseif getset == "set" then + local sh = constrained_tables[tbl].set_hooks + sh[field] = sh[field] or {} + table.insert(sh[field],func) + end +end diff --git a/src/create_party_menu.moon b/src/create_party_menu.moon new file mode 100644 index 0000000..b31c9d5 --- /dev/null +++ b/src/create_party_menu.moon @@ -0,0 +1,103 @@ +main = require "main" +world = require "world" +color = require "color" +ui = require "ui" +bp = require "broadphase" +action = require "action" +char = require "char" +player = require "player" +room = require "room" +import World from world +import Server from world +import LocalPlayer from player +import LobbyRoom from room + +mod = ... + +mod.node = am.group! +mod.node\append(am.translate(0,main.height/2)^ am.text("Creating lobby...", color.fg, "center","top")\tag("join_id")) +mod.loaded = false +mod.load = () -> + mod.loaded = true + print("Loading create party") + main.root("screen")\append(mod.node) + print("About to create server") + main["server"] = Server! + print("Created server") + mod.node("join_id").text = "Lobby id:" .. main.server.lobby_id + print("Set join id text") + main["world"] = World! + main.world\set_room(LobbyRoom!) + print("Created world") + main.world\join(main.server.lobby_id) + print("Joined my own lobby") + start_button = ui.create_any_button(mod.node,5,2,(-32*5)/2,0) + start_button.node("loc")\append(am.scale(1.5)^ am.translate(12,-10)^ am.text("GET SILLY", color.fg, "left", "top")) + char_selector = ui.create_char_selector2(mod.node) + char_right, char_left, char_text = char_selector[1], char_selector[2], char_selector[3] + mod.buttons = {char_left, char_right, start_button} --save to remove from the physworld later + classes = char.class_order + class_s = 1 + print("got char selector:",char_selector) + touch_indicator = am.group! + touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") + touch_loc = am.translate(0,0)^ touch_cursor + touch_indicator\append(touch_loc) + --Set everything transparent, give it color when we're connected + start_button.color = color.transparent + char_right.color = color.transparent + char_left.color = color.transparent + mod.node\action("click_interpreter",coroutine.create(()-> + while not main.world.client_open! + coroutine.yield! + char_left.color = color.white + char_right.color = color.white + char_text.text = "Tumbler" + main.world\set_local(LocalPlayer(am.eval_js("CLIENT.peer._id"),{},char.classes.Tumbler)) + main.world\load! + while true + if #main.win\active_touches! > 0 + touch = main.win\touch_position(1) + touch_cursor.color = vec4(1,1,1,1) + touch_loc.x = touch.x + touch_loc.y = touch.y + col_but = bp.check(touch.x, touch.y + 64) + if #col_but > 0 + touch_cursor.color = vec4(0.5,0.5,0.5,0.5) + if #col_but > 0 and main.win\touch_began(1) + if col_but[1] == start_button + print("Starting!") + action.start_game! + coroutine.yield! + if col_but[1] == char_left + print("char left") + class_s = class_s - 1 + start_button.color = color.white + if class_s == 0 + class_s = #classes + if col_but[1] == char_right + print("char right") + class_s = class_s + 1 + start_button.color = color.white + if class_s > #classes + class_s = 1 + if col_but[1] == char_left or col_but[1] == char_right + print("class_s",class_s) + print("classes:", classes) + char_text.text = classes[class_s] + action.request_class_change(classes[class_s]) + + else + touch_cursor.color = vec4(0,0,0,0) + coroutine.yield! + )) + mod.node\append(touch_indicator) + +mod.unload = () -> + print("Unloading create party") + main.root("screen")\remove(mod.node) + for _, button in pairs(mod.buttons) + bp.remove(button) + mod.loaded = false + +mod diff --git a/src/defeat_menu.moon b/src/defeat_menu.moon new file mode 100644 index 0000000..6810519 --- /dev/null +++ b/src/defeat_menu.moon @@ -0,0 +1,66 @@ + +main = require "main" +bp = require "broadphase" +color = require "color" +ui = require "ui" + +world = require "world" --delete when done +import Server from world +import World from world +mod = ... + +-- 4 parts? +nwidth = 6 +section_width = 32 +start_x = (-32)*(nwidth/2) +mod.node = am.group! +mod.node\append(am.sprite("data/defeat_screen.png")) +start_y = 0 +padding = 32 +buttonspecs = { + menu: { + y_off: start_y, + text: "Menu" + }, +} + + +mod.load = () -> + print("creating main menu") + main.root("screen")\append(mod.node) + for name, button_tbl in pairs buttonspecs + button_extra = ui.create_big_button(mod.node,6,start_x,button_tbl.y_off) + button_extra.node\append(am.translate(start_x+((nwidth*section_width)/2),button_tbl.y_off - 32)^ am.text(button_tbl.text,color.fg)) + mod[name] = button_extra + touch_indicator = am.group! + touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") + touch_loc = am.translate(0,0)^ touch_cursor + touch_indicator\append(touch_loc) + mod.node\append(am.translate(0,100)^ am.scale(3)^ am.text("You failed to\nremain silly")) + mod.node\action("click_interpreter",coroutine.create(()-> + while true + if #main.win\active_touches! > 0 + touch = main.win\touch_position(1) + touch_cursor.color = vec4(1,1,1,1) + touch_loc.x = touch.x + touch_loc.y = touch.y + col_but = bp.check(touch.x, touch.y+64) + if #col_but > 0 + touch_cursor.color = vec4(0.5,0.5,0.5,0.5) + if #col_but > 0 + print("Collided with button:",col_but) + print("mod.create prty was:",mod.create_party) + if col_but[1] == mod.menu + am.eval_js("location.reload()") + else + touch_cursor.color = vec4(0,0,0,0) + coroutine.yield! + )) + mod.node\append(touch_indicator) + +mod.unload = () -> + main.root("screen")\remove(mod.node) + bp.remove(mod.create_party) + bp.remove(mod.join_party) + +mod diff --git a/src/dispatch.moon b/src/dispatch.moon new file mode 100644 index 0000000..1455da8 --- /dev/null +++ b/src/dispatch.moon @@ -0,0 +1,49 @@ +--[[ Dispatches events ]] + +mod = ... +char = require "char" +import KeyInput from char +main = require "main" + +set_input = (inputs, key, value) -> + for input in *inputs + if input.key == key + --print("Setting", key, " to", value) + input.value = value + return + +controls = { + right: "right", + left: "left", + up: "up" + jump: "z", + down: "down" + dash: "c" + swing: "x" +} + +mod.control = (character) -> + character.node\action(coroutine.create(() -> + while true + for k,v in pairs controls + set_input(character.inputs,k,main.win\key_down(v)) + coroutine.yield() + )) + +mod.slime_ai = (character) -> + character.node\action(coroutine.create(() -> + time_offset = math.random() + last_action = am.current_time() + time_offset + while true + time_diff = am.current_time() - last_action + if time_diff > 1.3 + last_action = am.current_time() + math.random() + elseif time_diff > 1.1 + set_input(character.inputs,"jump_left",false) + elseif time_diff > 1 + set_input(character.inputs,"jump_left",true) + + coroutine.yield() + )) + +mod diff --git a/src/e_bethany.moon b/src/e_bethany.moon new file mode 100644 index 0000000..b589c79 --- /dev/null +++ b/src/e_bethany.moon @@ -0,0 +1,28 @@ +char = require "char" +reg = require "ability_reg" +require "a_brood" + +mod = ... + +mod.char = { + name: "Brooding Bethany", + cr: 8, + default_position: 2 + default_abilities: {reg.Brood}, + default_maxhp: 4, + default_hp: 1, + default_maxstamina: 1, + default_stamina: 1, + default_maxmana: 0, + default_mana: 0, + sprite:"data/e_brood.png", + select_action: () => + print("while selecting action, by abilities were:",@data.abilities) + @data.abilities[1] +} +for k,v in pairs(mod.char) + mod[k] = v + +char.enemies[mod.char.name] = mod.char + +mod diff --git a/src/e_child.moon b/src/e_child.moon new file mode 100644 index 0000000..90266b3 --- /dev/null +++ b/src/e_child.moon @@ -0,0 +1,28 @@ +char = require "char" +reg = require "ability_reg" +require "a_pass" + +mod = ... + +mod.char = { + name: "An actual child", + cr: 1, + default_position: 1 + default_abilities: {reg.Pass}, + default_maxhp: 1, + default_hp: 1, + default_maxstamina: 1, + default_stamina: 1, + default_maxmana: 0, + default_mana: 0, + sprite:"data/e_child.png", + select_action: () => + print("while selecting action, by abilities were:",@data.abilities) + @data.abilities[1] +} +for k,v in pairs(mod.char) + mod[k] = v + +char.enemies[mod.char.name] = mod.char + +mod diff --git a/src/e_mopey_marvin.moon b/src/e_mopey_marvin.moon new file mode 100644 index 0000000..d5685c7 --- /dev/null +++ b/src/e_mopey_marvin.moon @@ -0,0 +1,28 @@ +char = require "char" +reg = require "ability_reg" +require "a_mope" + +mod = ... + +mod.char = { + name: "Mopey Marvin", + cr: 5, + default_position: 2 + default_abilities: {reg.Mope}, + default_maxhp: 4, + default_hp: 1, + default_maxstamina: 1, + default_stamina: 1, + default_maxmana: 0, + default_mana: 0, + sprite:"data/e_mope.png", + select_action: () => + print("while selecting action, by abilities were:",@data.abilities) + @data.abilities[1] +} +for k,v in pairs(mod.char) + mod[k] = v + +char.enemies[mod.char.name] = mod.char + +mod diff --git a/src/e_rat.moon b/src/e_rat.moon new file mode 100644 index 0000000..d1357f3 --- /dev/null +++ b/src/e_rat.moon @@ -0,0 +1,31 @@ + +char = require "char" +reg = require "ability_reg" +require "a_rat_bite" + +mod = ... + +return mod + +mod.char = { + name: "Rat", + cr: 1, + default_position: 1 + default_abilities: {reg.RatBite}, + default_maxhp: 1, + default_hp: 1, + default_maxstamina: 1, + default_stamina: 1, + default_maxmana: 0, + default_mana: 0, + sprite:"data/e_rat.png", + select_action: () => + print("while selecting action, by abilities were:",@data.abilities) + @data.abilities[1] +} +for k,v in pairs(mod.char) + mod[k] = v + +char.enemies[mod.char.name] = mod.char + +mod diff --git a/src/e_ruminating_randy.moon b/src/e_ruminating_randy.moon new file mode 100644 index 0000000..b316aaf --- /dev/null +++ b/src/e_ruminating_randy.moon @@ -0,0 +1,28 @@ +char = require "char" +reg = require "ability_reg" +require "a_ruminate" + +mod = ... + +mod.char = { + name: "Ruminating Randy", + cr: 5, + default_position: 3 + default_abilities: {reg.Ruminate}, + default_maxhp: 4, + default_hp: 1, + default_maxstamina: 1, + default_stamina: 1, + default_maxmana: 0, + default_mana: 0, + sprite:"data/e_rum.png", + select_action: () => + print("while selecting action, by abilities were:",@data.abilities) + @data.abilities[1] +} +for k,v in pairs(mod.char) + mod[k] = v + +char.enemies[mod.char.name] = mod.char + +mod diff --git a/src/e_sullen_salley.moon b/src/e_sullen_salley.moon new file mode 100644 index 0000000..b82a4d3 --- /dev/null +++ b/src/e_sullen_salley.moon @@ -0,0 +1,29 @@ + +char = require "char" +reg = require "ability_reg" +require "a_sulk" + +mod = ... + +mod.char = { + name: "Sullen Sally", + cr: 2, + default_position: 1 + default_abilities: {reg.Sulk}, + default_maxhp: 3, + default_hp: 1, + default_maxstamina: 1, + default_stamina: 1, + default_maxmana: 0, + default_mana: 0, + sprite:"data/e_sullen.png", + select_action: () => + print("while selecting action, by abilities were:",@data.abilities) + @data.abilities[1] +} +for k,v in pairs(mod.char) + mod[k] = v + +char.enemies[mod.char.name] = mod.char + +mod diff --git a/src/ext.lua b/src/ext.lua new file mode 100644 index 0000000..a556dc7 --- /dev/null +++ b/src/ext.lua @@ -0,0 +1,66 @@ +-- Override tostring to display more info about the table +local old_tostring = tostring +local numtabs = 0 +local printed_tables = {} +--print = log +-- + --for obj in *@physobjs + --bp.add(t,obj.offset.x,obj.offset.y,obj.size.x,obj.size.y) + +local function tostring_helper(el) + assert(type(el) == "table", "Tried to call helper with something that was not a table, it was a " .. type(el)) + local mt = getmetatable(el) + if mt and mt.__tostring then + return mt.__tostring(el) + elseif printed_tables[el] == true then + return old_tostring(el) + else + printed_tables[el] = true + numtabs = numtabs + 1 + local strbuilder = {"{"} + for k,v in pairs(el) do + local key,value + if type(k) == "table" then + key = tostring_helper(k) + else + key = old_tostring(k) + end + if type(v) == "table" then + value = tostring_helper(v) + else + value = old_tostring(v) + end + strbuilder[#strbuilder + 1] = string.format("%s%s : %s", string.rep("\t",numtabs), key, value) + end + strbuilder[#strbuilder + 1] = string.rep("\t",numtabs - 1) .. "}" + numtabs = numtabs - 1 + return table.concat(strbuilder,"\n") + end + +end +function tostring(el) + printed_tables = {} + if type(el) == "table" then + return tostring_helper(el) + end + return old_tostring(el) +end + +-- Functions to save my hands +function printf(fmt, ...) + print(string.format(fmt,...)) +end +function errorf(fmt, ...) + --Our error isn't actually in this function, it's 1 above us (1) = 2 + error(string.format(fmt,...),2) +end +function assertf(bool, fmt, ...) + assert(type(fmt) == "string", "Assertf arg #2 was \"" .. type(fmt) .. "\", expected string") + if not bool then + args = {fmt} + for k,v in ipairs({...}) do + table.insert(args,tostring(v)) + end + error(string.format(unpack(args)),2) + end +end diff --git a/src/join_party_menu.moon b/src/join_party_menu.moon new file mode 100644 index 0000000..3f7d309 --- /dev/null +++ b/src/join_party_menu.moon @@ -0,0 +1,62 @@ + +main = require "main" +world = require "world" +color = require "color" +bp = require "broadphase" +ui = require "ui" +import World from world + +mod = ... + +mod.node = am.group! +mod.node\append(am.sprite("data/join.png")) +default_text = "..." +mod.load = () -> + mod.loaded = true + print("Loading join_party_menu") + main.root("screen")\append(mod.node) + mod.buttons = ui.create_join_input(mod.node) + mod.node\append(am.translate(0,-135)^ am.text(default_text, color.fg, "center","top")\tag("join_id")) + touch_indicator = am.group! + touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") + backspace = ui.create_any_button(mod.node,2,2,150,-25) + backspace.node\append(am.translate(164,-41)^ am.scale(2)^ am.text("<-", color.fg, "left","top")) + touch_loc = am.translate(0,0)^ touch_cursor + touch_indicator\append(touch_loc) + mod.node\append(touch_indicator) + print("char selector:",char_selector) + mod.node\action(coroutine.create(() -> + while true + if #main.win\active_touches! > 0 + touch = main.win\touch_position(1) + touch_cursor.color = vec4(1,1,1,1) + touch_loc.x = touch.x + touch_loc.y = touch.y + col_but = bp.check(touch.x, touch.y + 40) + n = mod.node("join_id") + if #col_but > 0 + touch_cursor.color = vec4(0.5,0.5,0.5,0.5) + if main.win\touch_began(1) + for _, button in pairs(mod.buttons) + if col_but[1] == button + if n.text == default_text + n.text = "" + print("Found pressed button",button) + n.text = n.text .. button.text + if col_but[1] == backspace + n.text = n.text\sub(1,string.len(n.text)-1) + if #n.text > 5 + print("Time to join!") + lobby = require("lobby_menu") + table.insert(main.action_queue,{lobby.load,{n.text}}) + mod.unload! + + coroutine.yield! + )) + +mod.unload = () -> + main.root("screen")\remove(mod.node) + for _, button in pairs(mod.buttons) + bp.remove(button) + +mod diff --git a/src/js/connect.js b/src/js/connect.js new file mode 100644 index 0000000..9811107 --- /dev/null +++ b/src/js/connect.js @@ -0,0 +1,8 @@ +(()=>{function e(e,t,n,r){Object.defineProperty(e,t,{get:n,set:r,enumerable:!0,configurable:!0})}function t(e){return e&&e.__esModule?e.default:e}class n{constructor(){this.chunkedMTU=16300,this._dataCount=1,this.chunk=e=>{let t=[],n=e.byteLength,r=Math.ceil(n/this.chunkedMTU),i=0,o=0;for(;o<n;){let s=Math.min(n,o+this.chunkedMTU),a=e.slice(o,s),c={__peerData:this._dataCount,n:i,data:a,total:r};t.push(c),o=s,i++}return this._dataCount++,t}}}class r{append_buffer(e){this.flush(),this._parts.push(e)}append(e){this._pieces.push(e)}flush(){if(this._pieces.length>0){let e=new Uint8Array(this._pieces);this._parts.push(e),this._pieces=[]}}toArrayBuffer(){let e=[];for(let t of this._parts)e.push(t);return function(e){let t=0;for(let n of e)t+=n.byteLength;let n=new Uint8Array(t),r=0;for(let t of e){let e=new Uint8Array(t.buffer,t.byteOffset,t.byteLength);n.set(e,r),r+=t.byteLength}return n}(e).buffer}constructor(){this.encoder=new TextEncoder,this._pieces=[],this._parts=[]}}function i(e){return new s(e).unpack()}function o(e){let t=new a,n=t.pack(e);return n instanceof Promise?n.then(()=>t.getBuffer()):t.getBuffer()}class s{unpack(){let e;let t=this.unpack_uint8();if(t<128)return t;if((224^t)<32)return(224^t)-32;if((e=160^t)<=15)return this.unpack_raw(e);if((e=176^t)<=15)return this.unpack_string(e);if((e=144^t)<=15)return this.unpack_array(e);if((e=128^t)<=15)return this.unpack_map(e);switch(t){case 192:return null;case 193:case 212:case 213:case 214:case 215:return;case 194:return!1;case 195:return!0;case 202:return this.unpack_float();case 203:return this.unpack_double();case 204:return this.unpack_uint8();case 205:return this.unpack_uint16();case 206:return this.unpack_uint32();case 207:return this.unpack_uint64();case 208:return this.unpack_int8();case 209:return this.unpack_int16();case 210:return this.unpack_int32();case 211:return this.unpack_int64();case 216:return e=this.unpack_uint16(),this.unpack_string(e);case 217:return e=this.unpack_uint32(),this.unpack_string(e);case 218:return e=this.unpack_uint16(),this.unpack_raw(e);case 219:return e=this.unpack_uint32(),this.unpack_raw(e);case 220:return e=this.unpack_uint16(),this.unpack_array(e);case 221:return e=this.unpack_uint32(),this.unpack_array(e);case 222:return e=this.unpack_uint16(),this.unpack_map(e);case 223:return e=this.unpack_uint32(),this.unpack_map(e)}}unpack_uint8(){let e=255&this.dataView[this.index];return this.index++,e}unpack_uint16(){let e=this.read(2),t=(255&e[0])*256+(255&e[1]);return this.index+=2,t}unpack_uint32(){let e=this.read(4),t=((256*e[0]+e[1])*256+e[2])*256+e[3];return this.index+=4,t}unpack_uint64(){let e=this.read(8),t=((((((256*e[0]+e[1])*256+e[2])*256+e[3])*256+e[4])*256+e[5])*256+e[6])*256+e[7];return this.index+=8,t}unpack_int8(){let e=this.unpack_uint8();return e<128?e:e-256}unpack_int16(){let e=this.unpack_uint16();return e<32768?e:e-65536}unpack_int32(){let e=this.unpack_uint32();return e<2147483648?e:e-4294967296}unpack_int64(){let e=this.unpack_uint64();return e<0x7fffffffffffffff?e:e-18446744073709552e3}unpack_raw(e){if(this.length<this.index+e)throw Error(`BinaryPackFailure: index is out of range ${this.index} ${e} ${this.length}`);let t=this.dataBuffer.slice(this.index,this.index+e);return this.index+=e,t}unpack_string(e){let t,n;let r=this.read(e),i=0,o="";for(;i<e;)(t=r[i])<160?(n=t,i++):(192^t)<32?(n=(31&t)<<6|63&r[i+1],i+=2):(224^t)<16?(n=(15&t)<<12|(63&r[i+1])<<6|63&r[i+2],i+=3):(n=(7&t)<<18|(63&r[i+1])<<12|(63&r[i+2])<<6|63&r[i+3],i+=4),o+=String.fromCodePoint(n);return this.index+=e,o}unpack_array(e){let t=Array(e);for(let n=0;n<e;n++)t[n]=this.unpack();return t}unpack_map(e){let t={};for(let n=0;n<e;n++)t[this.unpack()]=this.unpack();return t}unpack_float(){let e=this.unpack_uint32();return(0==e>>31?1:-1)*(8388607&e|8388608)*2**((e>>23&255)-127-23)}unpack_double(){let e=this.unpack_uint32(),t=this.unpack_uint32(),n=(e>>20&2047)-1023;return(0==e>>31?1:-1)*((1048575&e|1048576)*2**(n-20)+t*2**(n-52))}read(e){let t=this.index;if(t+e<=this.length)return this.dataView.subarray(t,t+e);throw Error("BinaryPackFailure: read index out of range")}constructor(e){this.index=0,this.dataBuffer=e,this.dataView=new Uint8Array(this.dataBuffer),this.length=this.dataBuffer.byteLength}}class a{getBuffer(){return this._bufferBuilder.toArrayBuffer()}pack(e){if("string"==typeof e)this.pack_string(e);else if("number"==typeof e)Math.floor(e)===e?this.pack_integer(e):this.pack_double(e);else if("boolean"==typeof e)!0===e?this._bufferBuilder.append(195):!1===e&&this._bufferBuilder.append(194);else if(void 0===e)this._bufferBuilder.append(192);else if("object"==typeof e){if(null===e)this._bufferBuilder.append(192);else{let t=e.constructor;if(e instanceof Array){let t=this.pack_array(e);if(t instanceof Promise)return t.then(()=>this._bufferBuilder.flush())}else if(e instanceof ArrayBuffer)this.pack_bin(new Uint8Array(e));else if("BYTES_PER_ELEMENT"in e)this.pack_bin(new Uint8Array(e.buffer,e.byteOffset,e.byteLength));else if(e instanceof Date)this.pack_string(e.toString());else if(e instanceof Blob)return e.arrayBuffer().then(e=>{this.pack_bin(new Uint8Array(e)),this._bufferBuilder.flush()});else if(t==Object||t.toString().startsWith("class")){let t=this.pack_object(e);if(t instanceof Promise)return t.then(()=>this._bufferBuilder.flush())}else throw Error(`Type "${t.toString()}" not yet supported`)}}else throw Error(`Type "${typeof e}" not yet supported`);this._bufferBuilder.flush()}pack_bin(e){let t=e.length;if(t<=15)this.pack_uint8(160+t);else if(t<=65535)this._bufferBuilder.append(218),this.pack_uint16(t);else if(t<=4294967295)this._bufferBuilder.append(219),this.pack_uint32(t);else throw Error("Invalid length");this._bufferBuilder.append_buffer(e)}pack_string(e){let t=this._textEncoder.encode(e),n=t.length;if(n<=15)this.pack_uint8(176+n);else if(n<=65535)this._bufferBuilder.append(216),this.pack_uint16(n);else if(n<=4294967295)this._bufferBuilder.append(217),this.pack_uint32(n);else throw Error("Invalid length");this._bufferBuilder.append_buffer(t)}pack_array(e){let t=e.length;if(t<=15)this.pack_uint8(144+t);else if(t<=65535)this._bufferBuilder.append(220),this.pack_uint16(t);else if(t<=4294967295)this._bufferBuilder.append(221),this.pack_uint32(t);else throw Error("Invalid length");let n=r=>{if(r<t){let t=this.pack(e[r]);return t instanceof Promise?t.then(()=>n(r+1)):n(r+1)}};return n(0)}pack_integer(e){if(e>=-32&&e<=127)this._bufferBuilder.append(255&e);else if(e>=0&&e<=255)this._bufferBuilder.append(204),this.pack_uint8(e);else if(e>=-128&&e<=127)this._bufferBuilder.append(208),this.pack_int8(e);else if(e>=0&&e<=65535)this._bufferBuilder.append(205),this.pack_uint16(e);else if(e>=-32768&&e<=32767)this._bufferBuilder.append(209),this.pack_int16(e);else if(e>=0&&e<=4294967295)this._bufferBuilder.append(206),this.pack_uint32(e);else if(e>=-2147483648&&e<=2147483647)this._bufferBuilder.append(210),this.pack_int32(e);else if(e>=-0x8000000000000000&&e<=0x7fffffffffffffff)this._bufferBuilder.append(211),this.pack_int64(e);else if(e>=0&&e<=18446744073709552e3)this._bufferBuilder.append(207),this.pack_uint64(e);else throw Error("Invalid integer")}pack_double(e){let t=0;e<0&&(t=1,e=-e);let n=Math.floor(Math.log(e)/Math.LN2),r=Math.floor((e/2**n-1)*4503599627370496),i=t<<31|n+1023<<20|r/4294967296&1048575;this._bufferBuilder.append(203),this.pack_int32(i),this.pack_int32(r%4294967296)}pack_object(e){let t=Object.keys(e),n=t.length;if(n<=15)this.pack_uint8(128+n);else if(n<=65535)this._bufferBuilder.append(222),this.pack_uint16(n);else if(n<=4294967295)this._bufferBuilder.append(223),this.pack_uint32(n);else throw Error("Invalid length");let r=n=>{if(n<t.length){let i=t[n];if(e.hasOwnProperty(i)){this.pack(i);let t=this.pack(e[i]);if(t instanceof Promise)return t.then(()=>r(n+1))}return r(n+1)}};return r(0)}pack_uint8(e){this._bufferBuilder.append(e)}pack_uint16(e){this._bufferBuilder.append(e>>8),this._bufferBuilder.append(255&e)}pack_uint32(e){let t=4294967295&e;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t)}pack_uint64(e){let t=e/4294967296,n=e%4294967296;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t),this._bufferBuilder.append((4278190080&n)>>>24),this._bufferBuilder.append((16711680&n)>>>16),this._bufferBuilder.append((65280&n)>>>8),this._bufferBuilder.append(255&n)}pack_int8(e){this._bufferBuilder.append(255&e)}pack_int16(e){this._bufferBuilder.append((65280&e)>>8),this._bufferBuilder.append(255&e)}pack_int32(e){this._bufferBuilder.append(e>>>24&255),this._bufferBuilder.append((16711680&e)>>>16),this._bufferBuilder.append((65280&e)>>>8),this._bufferBuilder.append(255&e)}pack_int64(e){let t=Math.floor(e/4294967296),n=e%4294967296;this._bufferBuilder.append((4278190080&t)>>>24),this._bufferBuilder.append((16711680&t)>>>16),this._bufferBuilder.append((65280&t)>>>8),this._bufferBuilder.append(255&t),this._bufferBuilder.append((4278190080&n)>>>24),this._bufferBuilder.append((16711680&n)>>>16),this._bufferBuilder.append((65280&n)>>>8),this._bufferBuilder.append(255&n)}constructor(){this._bufferBuilder=new r,this._textEncoder=new TextEncoder}}let c=!0,l=!0;function p(e,t,n){let r=e.match(t);return r&&r.length>=n&&parseInt(r[n],10)}function d(e,t,n){if(!e.RTCPeerConnection)return;let r=e.RTCPeerConnection.prototype,i=r.addEventListener;r.addEventListener=function(e,r){if(e!==t)return i.apply(this,arguments);let o=e=>{let t=n(e);t&&(r.handleEvent?r.handleEvent(t):r(t))};return this._eventMap=this._eventMap||{},this._eventMap[t]||(this._eventMap[t]=new Map),this._eventMap[t].set(r,o),i.apply(this,[e,o])};let o=r.removeEventListener;r.removeEventListener=function(e,n){if(e!==t||!this._eventMap||!this._eventMap[t]||!this._eventMap[t].has(n))return o.apply(this,arguments);let r=this._eventMap[t].get(n);return this._eventMap[t].delete(n),0===this._eventMap[t].size&&delete this._eventMap[t],0===Object.keys(this._eventMap).length&&delete this._eventMap,o.apply(this,[e,r])},Object.defineProperty(r,"on"+t,{get(){return this["_on"+t]},set(e){this["_on"+t]&&(this.removeEventListener(t,this["_on"+t]),delete this["_on"+t]),e&&this.addEventListener(t,this["_on"+t]=e)},enumerable:!0,configurable:!0})}function h(e){return"boolean"!=typeof e?Error("Argument type: "+typeof e+". Please use a boolean."):(c=e,e?"adapter.js logging disabled":"adapter.js logging enabled")}function u(e){return"boolean"!=typeof e?Error("Argument type: "+typeof e+". Please use a boolean."):(l=!e,"adapter.js deprecation warnings "+(e?"disabled":"enabled"))}function f(){"object"!=typeof window||c||"undefined"==typeof console||"function"!=typeof console.log||console.log.apply(console,arguments)}function m(e,t){l&&console.warn(e+" is deprecated, please use "+t+" instead.")}function g(e){return"[object Object]"===Object.prototype.toString.call(e)}function y(e,t,n){let r=n?"outbound-rtp":"inbound-rtp",i=new Map;if(null===t)return i;let o=[];return e.forEach(e=>{"track"===e.type&&e.trackIdentifier===t.id&&o.push(e)}),o.forEach(t=>{e.forEach(n=>{n.type===r&&n.trackId===t.id&&function e(t,n,r){!n||r.has(n.id)||(r.set(n.id,n),Object.keys(n).forEach(i=>{i.endsWith("Id")?e(t,t.get(n[i]),r):i.endsWith("Ids")&&n[i].forEach(n=>{e(t,t.get(n),r)})}))}(e,n,i)})}),i}var _,C,v,b,k,S,T,R,w,P,E,D,x,I,M,O,j={};function L(e,t){let n=e&&e.navigator;if(!n.mediaDevices)return;let r=function(e){if("object"!=typeof e||e.mandatory||e.optional)return e;let t={};return Object.keys(e).forEach(n=>{if("require"===n||"advanced"===n||"mediaSource"===n)return;let r="object"==typeof e[n]?e[n]:{ideal:e[n]};void 0!==r.exact&&"number"==typeof r.exact&&(r.min=r.max=r.exact);let i=function(e,t){return e?e+t.charAt(0).toUpperCase()+t.slice(1):"deviceId"===t?"sourceId":t};if(void 0!==r.ideal){t.optional=t.optional||[];let e={};"number"==typeof r.ideal?(e[i("min",n)]=r.ideal,t.optional.push(e),(e={})[i("max",n)]=r.ideal):e[i("",n)]=r.ideal,t.optional.push(e)}void 0!==r.exact&&"number"!=typeof r.exact?(t.mandatory=t.mandatory||{},t.mandatory[i("",n)]=r.exact):["min","max"].forEach(e=>{void 0!==r[e]&&(t.mandatory=t.mandatory||{},t.mandatory[i(e,n)]=r[e])})}),e.advanced&&(t.optional=(t.optional||[]).concat(e.advanced)),t},i=function(e,i){if(t.version>=61)return i(e);if((e=JSON.parse(JSON.stringify(e)))&&"object"==typeof e.audio){let t=function(e,t,n){t in e&&!(n in e)&&(e[n]=e[t],delete e[t])};t((e=JSON.parse(JSON.stringify(e))).audio,"autoGainControl","googAutoGainControl"),t(e.audio,"noiseSuppression","googNoiseSuppression"),e.audio=r(e.audio)}if(e&&"object"==typeof e.video){let o=e.video.facingMode;o=o&&("object"==typeof o?o:{ideal:o});let s=t.version<66;if(o&&("user"===o.exact||"environment"===o.exact||"user"===o.ideal||"environment"===o.ideal)&&!(n.mediaDevices.getSupportedConstraints&&n.mediaDevices.getSupportedConstraints().facingMode&&!s)){let t;if(delete e.video.facingMode,"environment"===o.exact||"environment"===o.ideal?t=["back","rear"]:("user"===o.exact||"user"===o.ideal)&&(t=["front"]),t)return n.mediaDevices.enumerateDevices().then(n=>{let s=(n=n.filter(e=>"videoinput"===e.kind)).find(e=>t.some(t=>e.label.toLowerCase().includes(t)));return!s&&n.length&&t.includes("back")&&(s=n[n.length-1]),s&&(e.video.deviceId=o.exact?{exact:s.deviceId}:{ideal:s.deviceId}),e.video=r(e.video),f("chrome: "+JSON.stringify(e)),i(e)})}e.video=r(e.video)}return f("chrome: "+JSON.stringify(e)),i(e)},o=function(e){return t.version>=64?e:{name:({PermissionDeniedError:"NotAllowedError",PermissionDismissedError:"NotAllowedError",InvalidStateError:"NotAllowedError",DevicesNotFoundError:"NotFoundError",ConstraintNotSatisfiedError:"OverconstrainedError",TrackStartError:"NotReadableError",MediaDeviceFailedDueToShutdown:"NotAllowedError",MediaDeviceKillSwitchOn:"NotAllowedError",TabCaptureError:"AbortError",ScreenCaptureError:"AbortError",DeviceCaptureError:"AbortError"})[e.name]||e.name,message:e.message,constraint:e.constraint||e.constraintName,toString(){return this.name+(this.message&&": ")+this.message}}};if(n.getUserMedia=(function(e,t,r){i(e,e=>{n.webkitGetUserMedia(e,t,e=>{r&&r(o(e))})})}).bind(n),n.mediaDevices.getUserMedia){let e=n.mediaDevices.getUserMedia.bind(n.mediaDevices);n.mediaDevices.getUserMedia=function(t){return i(t,t=>e(t).then(e=>{if(t.audio&&!e.getAudioTracks().length||t.video&&!e.getVideoTracks().length)throw e.getTracks().forEach(e=>{e.stop()}),new DOMException("","NotFoundError");return e},e=>Promise.reject(o(e))))}}}function A(e,t){if((!e.navigator.mediaDevices||!("getDisplayMedia"in e.navigator.mediaDevices))&&e.navigator.mediaDevices){if("function"!=typeof t){console.error("shimGetDisplayMedia: getSourceId argument is not a function");return}e.navigator.mediaDevices.getDisplayMedia=function(n){return t(n).then(t=>{let r=n.video&&n.video.width,i=n.video&&n.video.height,o=n.video&&n.video.frameRate;return n.video={mandatory:{chromeMediaSource:"desktop",chromeMediaSourceId:t,maxFrameRate:o||3}},r&&(n.video.mandatory.maxWidth=r),i&&(n.video.mandatory.maxHeight=i),e.navigator.mediaDevices.getUserMedia(n)})}}}function B(e){e.MediaStream=e.MediaStream||e.webkitMediaStream}function F(e){if("object"!=typeof e||!e.RTCPeerConnection||"ontrack"in e.RTCPeerConnection.prototype)d(e,"track",e=>(e.transceiver||Object.defineProperty(e,"transceiver",{value:{receiver:e.receiver}}),e));else{Object.defineProperty(e.RTCPeerConnection.prototype,"ontrack",{get(){return this._ontrack},set(e){this._ontrack&&this.removeEventListener("track",this._ontrack),this.addEventListener("track",this._ontrack=e)},enumerable:!0,configurable:!0});let t=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){return this._ontrackpoly||(this._ontrackpoly=t=>{t.stream.addEventListener("addtrack",n=>{let r;r=e.RTCPeerConnection.prototype.getReceivers?this.getReceivers().find(e=>e.track&&e.track.id===n.track.id):{track:n.track};let i=new Event("track");i.track=n.track,i.receiver=r,i.transceiver={receiver:r},i.streams=[t.stream],this.dispatchEvent(i)}),t.stream.getTracks().forEach(n=>{let r;r=e.RTCPeerConnection.prototype.getReceivers?this.getReceivers().find(e=>e.track&&e.track.id===n.id):{track:n};let i=new Event("track");i.track=n,i.receiver=r,i.transceiver={receiver:r},i.streams=[t.stream],this.dispatchEvent(i)})},this.addEventListener("addstream",this._ontrackpoly)),t.apply(this,arguments)}}}function U(e){if("object"==typeof e&&e.RTCPeerConnection&&!("getSenders"in e.RTCPeerConnection.prototype)&&"createDTMFSender"in e.RTCPeerConnection.prototype){let t=function(e,t){return{track:t,get dtmf(){return void 0===this._dtmf&&("audio"===t.kind?this._dtmf=e.createDTMFSender(t):this._dtmf=null),this._dtmf},_pc:e}};if(!e.RTCPeerConnection.prototype.getSenders){e.RTCPeerConnection.prototype.getSenders=function(){return this._senders=this._senders||[],this._senders.slice()};let n=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addTrack=function(e,r){let i=n.apply(this,arguments);return i||(i=t(this,e),this._senders.push(i)),i};let r=e.RTCPeerConnection.prototype.removeTrack;e.RTCPeerConnection.prototype.removeTrack=function(e){r.apply(this,arguments);let t=this._senders.indexOf(e);-1!==t&&this._senders.splice(t,1)}}let n=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(e){this._senders=this._senders||[],n.apply(this,[e]),e.getTracks().forEach(e=>{this._senders.push(t(this,e))})};let r=e.RTCPeerConnection.prototype.removeStream;e.RTCPeerConnection.prototype.removeStream=function(e){this._senders=this._senders||[],r.apply(this,[e]),e.getTracks().forEach(e=>{let t=this._senders.find(t=>t.track===e);t&&this._senders.splice(this._senders.indexOf(t),1)})}}else if("object"==typeof e&&e.RTCPeerConnection&&"getSenders"in e.RTCPeerConnection.prototype&&"createDTMFSender"in e.RTCPeerConnection.prototype&&e.RTCRtpSender&&!("dtmf"in e.RTCRtpSender.prototype)){let t=e.RTCPeerConnection.prototype.getSenders;e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e},Object.defineProperty(e.RTCRtpSender.prototype,"dtmf",{get(){return void 0===this._dtmf&&("audio"===this.track.kind?this._dtmf=this._pc.createDTMFSender(this.track):this._dtmf=null),this._dtmf}})}}function z(e){if(!e.RTCPeerConnection)return;let t=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){let[e,n,r]=arguments;if(arguments.length>0&&"function"==typeof e)return t.apply(this,arguments);if(0===t.length&&(0==arguments.length||"function"!=typeof e))return t.apply(this,[]);let i=function(e){let t={};return e.result().forEach(e=>{let n={id:e.id,timestamp:e.timestamp,type:{localcandidate:"local-candidate",remotecandidate:"remote-candidate"}[e.type]||e.type};e.names().forEach(t=>{n[t]=e.stat(t)}),t[n.id]=n}),t},o=function(e){return new Map(Object.keys(e).map(t=>[t,e[t]]))};return arguments.length>=2?t.apply(this,[function(e){n(o(i(e)))},e]):new Promise((e,n)=>{t.apply(this,[function(t){e(o(i(t)))},n])}).then(n,r)}}function N(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender&&e.RTCRtpReceiver))return;if(!("getStats"in e.RTCRtpSender.prototype)){let t=e.RTCPeerConnection.prototype.getSenders;t&&(e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e});let n=e.RTCPeerConnection.prototype.addTrack;n&&(e.RTCPeerConnection.prototype.addTrack=function(){let e=n.apply(this,arguments);return e._pc=this,e}),e.RTCRtpSender.prototype.getStats=function(){let e=this;return this._pc.getStats().then(t=>y(t,e.track,!0))}}if(!("getStats"in e.RTCRtpReceiver.prototype)){let t=e.RTCPeerConnection.prototype.getReceivers;t&&(e.RTCPeerConnection.prototype.getReceivers=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e}),d(e,"track",e=>(e.receiver._pc=e.srcElement,e)),e.RTCRtpReceiver.prototype.getStats=function(){let e=this;return this._pc.getStats().then(t=>y(t,e.track,!1))}}if(!("getStats"in e.RTCRtpSender.prototype&&"getStats"in e.RTCRtpReceiver.prototype))return;let t=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){if(arguments.length>0&&arguments[0]instanceof e.MediaStreamTrack){let e,t,n;let r=arguments[0];return(this.getSenders().forEach(t=>{t.track===r&&(e?n=!0:e=t)}),this.getReceivers().forEach(e=>(e.track===r&&(t?n=!0:t=e),e.track===r)),n||e&&t)?Promise.reject(new DOMException("There are more than one sender or receiver for the track.","InvalidAccessError")):e?e.getStats():t?t.getStats():Promise.reject(new DOMException("There is no sender or receiver for the track.","InvalidAccessError"))}return t.apply(this,arguments)}}function $(e){e.RTCPeerConnection.prototype.getLocalStreams=function(){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},Object.keys(this._shimmedLocalStreams).map(e=>this._shimmedLocalStreams[e][0])};let t=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addTrack=function(e,n){if(!n)return t.apply(this,arguments);this._shimmedLocalStreams=this._shimmedLocalStreams||{};let r=t.apply(this,arguments);return this._shimmedLocalStreams[n.id]?-1===this._shimmedLocalStreams[n.id].indexOf(r)&&this._shimmedLocalStreams[n.id].push(r):this._shimmedLocalStreams[n.id]=[n,r],r};let n=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(e){this._shimmedLocalStreams=this._shimmedLocalStreams||{},e.getTracks().forEach(e=>{if(this.getSenders().find(t=>t.track===e))throw new DOMException("Track already exists.","InvalidAccessError")});let t=this.getSenders();n.apply(this,arguments);let r=this.getSenders().filter(e=>-1===t.indexOf(e));this._shimmedLocalStreams[e.id]=[e].concat(r)};let r=e.RTCPeerConnection.prototype.removeStream;e.RTCPeerConnection.prototype.removeStream=function(e){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},delete this._shimmedLocalStreams[e.id],r.apply(this,arguments)};let i=e.RTCPeerConnection.prototype.removeTrack;e.RTCPeerConnection.prototype.removeTrack=function(e){return this._shimmedLocalStreams=this._shimmedLocalStreams||{},e&&Object.keys(this._shimmedLocalStreams).forEach(t=>{let n=this._shimmedLocalStreams[t].indexOf(e);-1!==n&&this._shimmedLocalStreams[t].splice(n,1),1===this._shimmedLocalStreams[t].length&&delete this._shimmedLocalStreams[t]}),i.apply(this,arguments)}}function J(e,t){if(!e.RTCPeerConnection)return;if(e.RTCPeerConnection.prototype.addTrack&&t.version>=65)return $(e);let n=e.RTCPeerConnection.prototype.getLocalStreams;e.RTCPeerConnection.prototype.getLocalStreams=function(){let e=n.apply(this);return this._reverseStreams=this._reverseStreams||{},e.map(e=>this._reverseStreams[e.id])};let r=e.RTCPeerConnection.prototype.addStream;e.RTCPeerConnection.prototype.addStream=function(t){if(this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{},t.getTracks().forEach(e=>{if(this.getSenders().find(t=>t.track===e))throw new DOMException("Track already exists.","InvalidAccessError")}),!this._reverseStreams[t.id]){let n=new e.MediaStream(t.getTracks());this._streams[t.id]=n,this._reverseStreams[n.id]=t,t=n}r.apply(this,[t])};let i=e.RTCPeerConnection.prototype.removeStream;function o(e,t){let n=t.sdp;return Object.keys(e._reverseStreams||[]).forEach(t=>{let r=e._reverseStreams[t],i=e._streams[r.id];n=n.replace(RegExp(i.id,"g"),r.id)}),new RTCSessionDescription({type:t.type,sdp:n})}e.RTCPeerConnection.prototype.removeStream=function(e){this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{},i.apply(this,[this._streams[e.id]||e]),delete this._reverseStreams[this._streams[e.id]?this._streams[e.id].id:e.id],delete this._streams[e.id]},e.RTCPeerConnection.prototype.addTrack=function(t,n){if("closed"===this.signalingState)throw new DOMException("The RTCPeerConnection's signalingState is 'closed'.","InvalidStateError");let r=[].slice.call(arguments,1);if(1!==r.length||!r[0].getTracks().find(e=>e===t))throw new DOMException("The adapter.js addTrack polyfill only supports a single stream which is associated with the specified track.","NotSupportedError");if(this.getSenders().find(e=>e.track===t))throw new DOMException("Track already exists.","InvalidAccessError");this._streams=this._streams||{},this._reverseStreams=this._reverseStreams||{};let i=this._streams[n.id];if(i)i.addTrack(t),Promise.resolve().then(()=>{this.dispatchEvent(new Event("negotiationneeded"))});else{let r=new e.MediaStream([t]);this._streams[n.id]=r,this._reverseStreams[r.id]=n,this.addStream(r)}return this.getSenders().find(e=>e.track===t)},["createOffer","createAnswer"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){let e=arguments,t=arguments.length&&"function"==typeof arguments[0];return t?n.apply(this,[t=>{let n=o(this,t);e[0].apply(null,[n])},t=>{e[1]&&e[1].apply(null,t)},arguments[2]]):n.apply(this,arguments).then(e=>o(this,e))}})[t]});let s=e.RTCPeerConnection.prototype.setLocalDescription;e.RTCPeerConnection.prototype.setLocalDescription=function(){var e,t;let n;return arguments.length&&arguments[0].type&&(arguments[0]=(e=this,t=arguments[0],n=t.sdp,Object.keys(e._reverseStreams||[]).forEach(t=>{let r=e._reverseStreams[t],i=e._streams[r.id];n=n.replace(RegExp(r.id,"g"),i.id)}),new RTCSessionDescription({type:t.type,sdp:n}))),s.apply(this,arguments)};let a=Object.getOwnPropertyDescriptor(e.RTCPeerConnection.prototype,"localDescription");Object.defineProperty(e.RTCPeerConnection.prototype,"localDescription",{get(){let e=a.get.apply(this);return""===e.type?e:o(this,e)}}),e.RTCPeerConnection.prototype.removeTrack=function(e){let t;if("closed"===this.signalingState)throw new DOMException("The RTCPeerConnection's signalingState is 'closed'.","InvalidStateError");if(!e._pc)throw new DOMException("Argument 1 of RTCPeerConnection.removeTrack does not implement interface RTCRtpSender.","TypeError");if(e._pc!==this)throw new DOMException("Sender was not created by this connection.","InvalidAccessError");this._streams=this._streams||{},Object.keys(this._streams).forEach(n=>{this._streams[n].getTracks().find(t=>e.track===t)&&(t=this._streams[n])}),t&&(1===t.getTracks().length?this.removeStream(this._reverseStreams[t.id]):t.removeTrack(e.track),this.dispatchEvent(new Event("negotiationneeded")))}}function V(e,t){!e.RTCPeerConnection&&e.webkitRTCPeerConnection&&(e.RTCPeerConnection=e.webkitRTCPeerConnection),e.RTCPeerConnection&&t.version<53&&["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){return arguments[0]=new("addIceCandidate"===t?e.RTCIceCandidate:e.RTCSessionDescription)(arguments[0]),n.apply(this,arguments)}})[t]})}function G(e,t){d(e,"negotiationneeded",e=>{let n=e.target;if(!(t.version<72)&&(!n.getConfiguration||"plan-b"!==n.getConfiguration().sdpSemantics)||"stable"===n.signalingState)return e})}e(j,"shimMediaStream",()=>B),e(j,"shimOnTrack",()=>F),e(j,"shimGetSendersWithDtmf",()=>U),e(j,"shimGetStats",()=>z),e(j,"shimSenderReceiverGetStats",()=>N),e(j,"shimAddTrackRemoveTrackWithNative",()=>$),e(j,"shimAddTrackRemoveTrack",()=>J),e(j,"shimPeerConnection",()=>V),e(j,"fixNegotiationNeeded",()=>G),e(j,"shimGetUserMedia",()=>L),e(j,"shimGetDisplayMedia",()=>A);var W={};function H(e,t){let n=e&&e.navigator,r=e&&e.MediaStreamTrack;if(n.getUserMedia=function(e,t,r){m("navigator.getUserMedia","navigator.mediaDevices.getUserMedia"),n.mediaDevices.getUserMedia(e).then(t,r)},!(t.version>55&&"autoGainControl"in n.mediaDevices.getSupportedConstraints())){let e=function(e,t,n){t in e&&!(n in e)&&(e[n]=e[t],delete e[t])},t=n.mediaDevices.getUserMedia.bind(n.mediaDevices);if(n.mediaDevices.getUserMedia=function(n){return"object"==typeof n&&"object"==typeof n.audio&&(e((n=JSON.parse(JSON.stringify(n))).audio,"autoGainControl","mozAutoGainControl"),e(n.audio,"noiseSuppression","mozNoiseSuppression")),t(n)},r&&r.prototype.getSettings){let t=r.prototype.getSettings;r.prototype.getSettings=function(){let n=t.apply(this,arguments);return e(n,"mozAutoGainControl","autoGainControl"),e(n,"mozNoiseSuppression","noiseSuppression"),n}}if(r&&r.prototype.applyConstraints){let t=r.prototype.applyConstraints;r.prototype.applyConstraints=function(n){return"audio"===this.kind&&"object"==typeof n&&(e(n=JSON.parse(JSON.stringify(n)),"autoGainControl","mozAutoGainControl"),e(n,"noiseSuppression","mozNoiseSuppression")),t.apply(this,[n])}}}}function Y(e,t){e.navigator.mediaDevices&&"getDisplayMedia"in e.navigator.mediaDevices||!e.navigator.mediaDevices||(e.navigator.mediaDevices.getDisplayMedia=function(n){if(!(n&&n.video)){let e=new DOMException("getDisplayMedia without video constraints is undefined");return e.name="NotFoundError",e.code=8,Promise.reject(e)}return!0===n.video?n.video={mediaSource:t}:n.video.mediaSource=t,e.navigator.mediaDevices.getUserMedia(n)})}function K(e){"object"==typeof e&&e.RTCTrackEvent&&"receiver"in e.RTCTrackEvent.prototype&&!("transceiver"in e.RTCTrackEvent.prototype)&&Object.defineProperty(e.RTCTrackEvent.prototype,"transceiver",{get(){return{receiver:this.receiver}}})}function X(e,t){if("object"!=typeof e||!(e.RTCPeerConnection||e.mozRTCPeerConnection))return;!e.RTCPeerConnection&&e.mozRTCPeerConnection&&(e.RTCPeerConnection=e.mozRTCPeerConnection),t.version<53&&["setLocalDescription","setRemoteDescription","addIceCandidate"].forEach(function(t){let n=e.RTCPeerConnection.prototype[t];e.RTCPeerConnection.prototype[t]=({[t](){return arguments[0]=new("addIceCandidate"===t?e.RTCIceCandidate:e.RTCSessionDescription)(arguments[0]),n.apply(this,arguments)}})[t]});let n={inboundrtp:"inbound-rtp",outboundrtp:"outbound-rtp",candidatepair:"candidate-pair",localcandidate:"local-candidate",remotecandidate:"remote-candidate"},r=e.RTCPeerConnection.prototype.getStats;e.RTCPeerConnection.prototype.getStats=function(){let[e,i,o]=arguments;return r.apply(this,[e||null]).then(e=>{if(t.version<53&&!i)try{e.forEach(e=>{e.type=n[e.type]||e.type})}catch(t){if("TypeError"!==t.name)throw t;e.forEach((t,r)=>{e.set(r,Object.assign({},t,{type:n[t.type]||t.type}))})}return e}).then(i,o)}}function q(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender)||e.RTCRtpSender&&"getStats"in e.RTCRtpSender.prototype)return;let t=e.RTCPeerConnection.prototype.getSenders;t&&(e.RTCPeerConnection.prototype.getSenders=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e});let n=e.RTCPeerConnection.prototype.addTrack;n&&(e.RTCPeerConnection.prototype.addTrack=function(){let e=n.apply(this,arguments);return e._pc=this,e}),e.RTCRtpSender.prototype.getStats=function(){return this.track?this._pc.getStats(this.track):Promise.resolve(new Map)}}function Q(e){if(!("object"==typeof e&&e.RTCPeerConnection&&e.RTCRtpSender)||e.RTCRtpSender&&"getStats"in e.RTCRtpReceiver.prototype)return;let t=e.RTCPeerConnection.prototype.getReceivers;t&&(e.RTCPeerConnection.prototype.getReceivers=function(){let e=t.apply(this,[]);return e.forEach(e=>e._pc=this),e}),d(e,"track",e=>(e.receiver._pc=e.srcElement,e)),e.RTCRtpReceiver.prototype.getStats=function(){return this._pc.getStats(this.track)}}function Z(e){!e.RTCPeerConnection||"removeStream"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.removeStream=function(e){m("removeStream","removeTrack"),this.getSenders().forEach(t=>{t.track&&e.getTracks().includes(t.track)&&this.removeTrack(t)})})}function ee(e){e.DataChannel&&!e.RTCDataChannel&&(e.RTCDataChannel=e.DataChannel)}function et(e){if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.addTransceiver;t&&(e.RTCPeerConnection.prototype.addTransceiver=function(){this.setParametersPromises=[];let e=arguments[1]&&arguments[1].sendEncodings;void 0===e&&(e=[]);let n=(e=[...e]).length>0;n&&e.forEach(e=>{if("rid"in e&&!/^[a-z0-9]{0,16}$/i.test(e.rid))throw TypeError("Invalid RID value provided.");if("scaleResolutionDownBy"in e&&!(parseFloat(e.scaleResolutionDownBy)>=1))throw RangeError("scale_resolution_down_by must be >= 1.0");if("maxFramerate"in e&&!(parseFloat(e.maxFramerate)>=0))throw RangeError("max_framerate must be >= 0.0")});let r=t.apply(this,arguments);if(n){let{sender:t}=r,n=t.getParameters();"encodings"in n&&(1!==n.encodings.length||0!==Object.keys(n.encodings[0]).length)||(n.encodings=e,t.sendEncodings=e,this.setParametersPromises.push(t.setParameters(n).then(()=>{delete t.sendEncodings}).catch(()=>{delete t.sendEncodings})))}return r})}function en(e){if(!("object"==typeof e&&e.RTCRtpSender))return;let t=e.RTCRtpSender.prototype.getParameters;t&&(e.RTCRtpSender.prototype.getParameters=function(){let e=t.apply(this,arguments);return"encodings"in e||(e.encodings=[].concat(this.sendEncodings||[{}])),e})}function er(e){if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.createOffer;e.RTCPeerConnection.prototype.createOffer=function(){return this.setParametersPromises&&this.setParametersPromises.length?Promise.all(this.setParametersPromises).then(()=>t.apply(this,arguments)).finally(()=>{this.setParametersPromises=[]}):t.apply(this,arguments)}}function ei(e){if(!("object"==typeof e&&e.RTCPeerConnection))return;let t=e.RTCPeerConnection.prototype.createAnswer;e.RTCPeerConnection.prototype.createAnswer=function(){return this.setParametersPromises&&this.setParametersPromises.length?Promise.all(this.setParametersPromises).then(()=>t.apply(this,arguments)).finally(()=>{this.setParametersPromises=[]}):t.apply(this,arguments)}}e(W,"shimOnTrack",()=>K),e(W,"shimPeerConnection",()=>X),e(W,"shimSenderGetStats",()=>q),e(W,"shimReceiverGetStats",()=>Q),e(W,"shimRemoveStream",()=>Z),e(W,"shimRTCDataChannel",()=>ee),e(W,"shimAddTransceiver",()=>et),e(W,"shimGetParameters",()=>en),e(W,"shimCreateOffer",()=>er),e(W,"shimCreateAnswer",()=>ei),e(W,"shimGetUserMedia",()=>H),e(W,"shimGetDisplayMedia",()=>Y);var eo={};function es(e){if("object"==typeof e&&e.RTCPeerConnection){if("getLocalStreams"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.getLocalStreams=function(){return this._localStreams||(this._localStreams=[]),this._localStreams}),!("addStream"in e.RTCPeerConnection.prototype)){let t=e.RTCPeerConnection.prototype.addTrack;e.RTCPeerConnection.prototype.addStream=function(e){this._localStreams||(this._localStreams=[]),this._localStreams.includes(e)||this._localStreams.push(e),e.getAudioTracks().forEach(n=>t.call(this,n,e)),e.getVideoTracks().forEach(n=>t.call(this,n,e))},e.RTCPeerConnection.prototype.addTrack=function(e,...n){return n&&n.forEach(e=>{this._localStreams?this._localStreams.includes(e)||this._localStreams.push(e):this._localStreams=[e]}),t.apply(this,arguments)}}"removeStream"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.removeStream=function(e){this._localStreams||(this._localStreams=[]);let t=this._localStreams.indexOf(e);if(-1===t)return;this._localStreams.splice(t,1);let n=e.getTracks();this.getSenders().forEach(e=>{n.includes(e.track)&&this.removeTrack(e)})})}}function ea(e){if("object"==typeof e&&e.RTCPeerConnection&&("getRemoteStreams"in e.RTCPeerConnection.prototype||(e.RTCPeerConnection.prototype.getRemoteStreams=function(){return this._remoteStreams?this._remoteStreams:[]}),!("onaddstream"in e.RTCPeerConnection.prototype))){Object.defineProperty(e.RTCPeerConnection.prototype,"onaddstream",{get(){return this._onaddstream},set(e){this._onaddstream&&(this.removeEventListener("addstream",this._onaddstream),this.removeEventListener("track",this._onaddstreampoly)),this.addEventListener("addstream",this._onaddstream=e),this.addEventListener("track",this._onaddstreampoly=e=>{e.streams.forEach(e=>{if(this._remoteStreams||(this._remoteStreams=[]),this._remoteStreams.includes(e))return;this._remoteStreams.push(e);let t=new Event("addstream");t.stream=e,this.dispatchEvent(t)})})}});let t=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){let e=this;return this._onaddstreampoly||this.addEventListener("track",this._onaddstreampoly=function(t){t.streams.forEach(t=>{if(e._remoteStreams||(e._remoteStreams=[]),e._remoteStreams.indexOf(t)>=0)return;e._remoteStreams.push(t);let n=new Event("addstream");n.stream=t,e.dispatchEvent(n)})}),t.apply(e,arguments)}}}function ec(e){if("object"!=typeof e||!e.RTCPeerConnection)return;let t=e.RTCPeerConnection.prototype,n=t.createOffer,r=t.createAnswer,i=t.setLocalDescription,o=t.setRemoteDescription,s=t.addIceCandidate;t.createOffer=function(e,t){let r=arguments.length>=2?arguments[2]:arguments[0],i=n.apply(this,[r]);return t?(i.then(e,t),Promise.resolve()):i},t.createAnswer=function(e,t){let n=arguments.length>=2?arguments[2]:arguments[0],i=r.apply(this,[n]);return t?(i.then(e,t),Promise.resolve()):i};let a=function(e,t,n){let r=i.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r};t.setLocalDescription=a,a=function(e,t,n){let r=o.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r},t.setRemoteDescription=a,a=function(e,t,n){let r=s.apply(this,[e]);return n?(r.then(t,n),Promise.resolve()):r},t.addIceCandidate=a}function el(e){let t=e&&e.navigator;if(t.mediaDevices&&t.mediaDevices.getUserMedia){let e=t.mediaDevices,n=e.getUserMedia.bind(e);t.mediaDevices.getUserMedia=e=>n(ep(e))}!t.getUserMedia&&t.mediaDevices&&t.mediaDevices.getUserMedia&&(t.getUserMedia=(function(e,n,r){t.mediaDevices.getUserMedia(e).then(n,r)}).bind(t))}function ep(e){return e&&void 0!==e.video?Object.assign({},e,{video:function e(t){return g(t)?Object.keys(t).reduce(function(n,r){let i=g(t[r]),o=i?e(t[r]):t[r],s=i&&!Object.keys(o).length;return void 0===o||s?n:Object.assign(n,{[r]:o})},{}):t}(e.video)}):e}function ed(e){if(!e.RTCPeerConnection)return;let t=e.RTCPeerConnection;e.RTCPeerConnection=function(e,n){if(e&&e.iceServers){let t=[];for(let n=0;n<e.iceServers.length;n++){let r=e.iceServers[n];void 0===r.urls&&r.url?(m("RTCIceServer.url","RTCIceServer.urls"),(r=JSON.parse(JSON.stringify(r))).urls=r.url,delete r.url,t.push(r)):t.push(e.iceServers[n])}e.iceServers=t}return new t(e,n)},e.RTCPeerConnection.prototype=t.prototype,"generateCertificate"in t&&Object.defineProperty(e.RTCPeerConnection,"generateCertificate",{get:()=>t.generateCertificate})}function eh(e){"object"==typeof e&&e.RTCTrackEvent&&"receiver"in e.RTCTrackEvent.prototype&&!("transceiver"in e.RTCTrackEvent.prototype)&&Object.defineProperty(e.RTCTrackEvent.prototype,"transceiver",{get(){return{receiver:this.receiver}}})}function eu(e){let t=e.RTCPeerConnection.prototype.createOffer;e.RTCPeerConnection.prototype.createOffer=function(e){if(e){void 0!==e.offerToReceiveAudio&&(e.offerToReceiveAudio=!!e.offerToReceiveAudio);let t=this.getTransceivers().find(e=>"audio"===e.receiver.track.kind);!1===e.offerToReceiveAudio&&t?"sendrecv"===t.direction?t.setDirection?t.setDirection("sendonly"):t.direction="sendonly":"recvonly"===t.direction&&(t.setDirection?t.setDirection("inactive"):t.direction="inactive"):!0!==e.offerToReceiveAudio||t||this.addTransceiver("audio",{direction:"recvonly"}),void 0!==e.offerToReceiveVideo&&(e.offerToReceiveVideo=!!e.offerToReceiveVideo);let n=this.getTransceivers().find(e=>"video"===e.receiver.track.kind);!1===e.offerToReceiveVideo&&n?"sendrecv"===n.direction?n.setDirection?n.setDirection("sendonly"):n.direction="sendonly":"recvonly"===n.direction&&(n.setDirection?n.setDirection("inactive"):n.direction="inactive"):!0!==e.offerToReceiveVideo||n||this.addTransceiver("video",{direction:"recvonly"})}return t.apply(this,arguments)}}function ef(e){"object"!=typeof e||e.AudioContext||(e.AudioContext=e.webkitAudioContext)}e(eo,"shimLocalStreamsAPI",()=>es),e(eo,"shimRemoteStreamsAPI",()=>ea),e(eo,"shimCallbacksAPI",()=>ec),e(eo,"shimGetUserMedia",()=>el),e(eo,"shimConstraints",()=>ep),e(eo,"shimRTCIceServerUrls",()=>ed),e(eo,"shimTrackEventTransceiver",()=>eh),e(eo,"shimCreateOfferLegacy",()=>eu),e(eo,"shimAudioContext",()=>ef);var em={};e(em,"shimRTCIceCandidate",()=>e_),e(em,"shimRTCIceCandidateRelayProtocol",()=>eC),e(em,"shimMaxMessageSize",()=>ev),e(em,"shimSendThrowTypeError",()=>eb),e(em,"shimConnectionState",()=>ek),e(em,"removeExtmapAllowMixed",()=>eS),e(em,"shimAddIceCandidateNullOrEmpty",()=>eT),e(em,"shimParameterlessSetLocalDescription",()=>eR);var eg={};let ey={};function e_(e){if(!e.RTCIceCandidate||e.RTCIceCandidate&&"foundation"in e.RTCIceCandidate.prototype)return;let n=e.RTCIceCandidate;e.RTCIceCandidate=function(e){if("object"==typeof e&&e.candidate&&0===e.candidate.indexOf("a=")&&((e=JSON.parse(JSON.stringify(e))).candidate=e.candidate.substring(2)),e.candidate&&e.candidate.length){let r=new n(e),i=t(eg).parseCandidate(e.candidate);for(let e in i)e in r||Object.defineProperty(r,e,{value:i[e]});return r.toJSON=function(){return{candidate:r.candidate,sdpMid:r.sdpMid,sdpMLineIndex:r.sdpMLineIndex,usernameFragment:r.usernameFragment}},r}return new n(e)},e.RTCIceCandidate.prototype=n.prototype,d(e,"icecandidate",t=>(t.candidate&&Object.defineProperty(t,"candidate",{value:new e.RTCIceCandidate(t.candidate),writable:"false"}),t))}function eC(e){!e.RTCIceCandidate||e.RTCIceCandidate&&"relayProtocol"in e.RTCIceCandidate.prototype||d(e,"icecandidate",e=>{if(e.candidate){let n=t(eg).parseCandidate(e.candidate.candidate);"relay"===n.type&&(e.candidate.relayProtocol=({0:"tls",1:"tcp",2:"udp"})[n.priority>>24])}return e})}function ev(e,n){if(!e.RTCPeerConnection)return;"sctp"in e.RTCPeerConnection.prototype||Object.defineProperty(e.RTCPeerConnection.prototype,"sctp",{get(){return void 0===this._sctp?null:this._sctp}});let r=function(e){if(!e||!e.sdp)return!1;let n=t(eg).splitSections(e.sdp);return n.shift(),n.some(e=>{let n=t(eg).parseMLine(e);return n&&"application"===n.kind&&-1!==n.protocol.indexOf("SCTP")})},i=function(e){let t=e.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/);if(null===t||t.length<2)return -1;let n=parseInt(t[1],10);return n!=n?-1:n},o=function(e){let t=65536;return"firefox"===n.browser&&(t=n.version<57?-1===e?16384:2147483637:n.version<60?57===n.version?65535:65536:2147483637),t},s=function(e,r){let i=65536;"firefox"===n.browser&&57===n.version&&(i=65535);let o=t(eg).matchPrefix(e.sdp,"a=max-message-size:");return o.length>0?i=parseInt(o[0].substring(19),10):"firefox"===n.browser&&-1!==r&&(i=2147483637),i},a=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(){if(this._sctp=null,"chrome"===n.browser&&n.version>=76){let{sdpSemantics:e}=this.getConfiguration();"plan-b"===e&&Object.defineProperty(this,"sctp",{get(){return void 0===this._sctp?null:this._sctp},enumerable:!0,configurable:!0})}if(r(arguments[0])){let e;let t=i(arguments[0]),n=o(t),r=s(arguments[0],t);e=0===n&&0===r?Number.POSITIVE_INFINITY:0===n||0===r?Math.max(n,r):Math.min(n,r);let a={};Object.defineProperty(a,"maxMessageSize",{get:()=>e}),this._sctp=a}return a.apply(this,arguments)}}function eb(e){if(!(e.RTCPeerConnection&&"createDataChannel"in e.RTCPeerConnection.prototype))return;function t(e,t){let n=e.send;e.send=function(){let r=arguments[0],i=r.length||r.size||r.byteLength;if("open"===e.readyState&&t.sctp&&i>t.sctp.maxMessageSize)throw TypeError("Message too large (can send a maximum of "+t.sctp.maxMessageSize+" bytes)");return n.apply(e,arguments)}}let n=e.RTCPeerConnection.prototype.createDataChannel;e.RTCPeerConnection.prototype.createDataChannel=function(){let e=n.apply(this,arguments);return t(e,this),e},d(e,"datachannel",e=>(t(e.channel,e.target),e))}function ek(e){if(!e.RTCPeerConnection||"connectionState"in e.RTCPeerConnection.prototype)return;let t=e.RTCPeerConnection.prototype;Object.defineProperty(t,"connectionState",{get(){return({completed:"connected",checking:"connecting"})[this.iceConnectionState]||this.iceConnectionState},enumerable:!0,configurable:!0}),Object.defineProperty(t,"onconnectionstatechange",{get(){return this._onconnectionstatechange||null},set(e){this._onconnectionstatechange&&(this.removeEventListener("connectionstatechange",this._onconnectionstatechange),delete this._onconnectionstatechange),e&&this.addEventListener("connectionstatechange",this._onconnectionstatechange=e)},enumerable:!0,configurable:!0}),["setLocalDescription","setRemoteDescription"].forEach(e=>{let n=t[e];t[e]=function(){return this._connectionstatechangepoly||(this._connectionstatechangepoly=e=>{let t=e.target;if(t._lastConnectionState!==t.connectionState){t._lastConnectionState=t.connectionState;let n=new Event("connectionstatechange",e);t.dispatchEvent(n)}return e},this.addEventListener("iceconnectionstatechange",this._connectionstatechangepoly)),n.apply(this,arguments)}})}function eS(e,t){if(!e.RTCPeerConnection||"chrome"===t.browser&&t.version>=71||"safari"===t.browser&&t.version>=605)return;let n=e.RTCPeerConnection.prototype.setRemoteDescription;e.RTCPeerConnection.prototype.setRemoteDescription=function(t){if(t&&t.sdp&&-1!==t.sdp.indexOf("\na=extmap-allow-mixed")){let n=t.sdp.split("\n").filter(e=>"a=extmap-allow-mixed"!==e.trim()).join("\n");e.RTCSessionDescription&&t instanceof e.RTCSessionDescription?arguments[0]=new e.RTCSessionDescription({type:t.type,sdp:n}):t.sdp=n}return n.apply(this,arguments)}}function eT(e,t){if(!(e.RTCPeerConnection&&e.RTCPeerConnection.prototype))return;let n=e.RTCPeerConnection.prototype.addIceCandidate;n&&0!==n.length&&(e.RTCPeerConnection.prototype.addIceCandidate=function(){return arguments[0]?("chrome"===t.browser&&t.version<78||"firefox"===t.browser&&t.version<68||"safari"===t.browser)&&arguments[0]&&""===arguments[0].candidate?Promise.resolve():n.apply(this,arguments):(arguments[1]&&arguments[1].apply(null),Promise.resolve())})}function eR(e,t){if(!(e.RTCPeerConnection&&e.RTCPeerConnection.prototype))return;let n=e.RTCPeerConnection.prototype.setLocalDescription;n&&0!==n.length&&(e.RTCPeerConnection.prototype.setLocalDescription=function(){let e=arguments[0]||{};if("object"!=typeof e||e.type&&e.sdp)return n.apply(this,arguments);if(!(e={type:e.type,sdp:e.sdp}).type)switch(this.signalingState){case"stable":case"have-local-offer":case"have-remote-pranswer":e.type="offer";break;default:e.type="answer"}return e.sdp||"offer"!==e.type&&"answer"!==e.type?n.apply(this,[e]):("offer"===e.type?this.createOffer:this.createAnswer).apply(this).then(e=>n.apply(this,[e]))})}ey.generateIdentifier=function(){return Math.random().toString(36).substring(2,12)},ey.localCName=ey.generateIdentifier(),ey.splitLines=function(e){return e.trim().split("\n").map(e=>e.trim())},ey.splitSections=function(e){return e.split("\nm=").map((e,t)=>(t>0?"m="+e:e).trim()+"\r\n")},ey.getDescription=function(e){let t=ey.splitSections(e);return t&&t[0]},ey.getMediaSections=function(e){let t=ey.splitSections(e);return t.shift(),t},ey.matchPrefix=function(e,t){return ey.splitLines(e).filter(e=>0===e.indexOf(t))},ey.parseCandidate=function(e){let t;let n={foundation:(t=0===e.indexOf("a=candidate:")?e.substring(12).split(" "):e.substring(10).split(" "))[0],component:{1:"rtp",2:"rtcp"}[t[1]]||t[1],protocol:t[2].toLowerCase(),priority:parseInt(t[3],10),ip:t[4],address:t[4],port:parseInt(t[5],10),type:t[7]};for(let e=8;e<t.length;e+=2)switch(t[e]){case"raddr":n.relatedAddress=t[e+1];break;case"rport":n.relatedPort=parseInt(t[e+1],10);break;case"tcptype":n.tcpType=t[e+1];break;case"ufrag":n.ufrag=t[e+1],n.usernameFragment=t[e+1];break;default:void 0===n[t[e]]&&(n[t[e]]=t[e+1])}return n},ey.writeCandidate=function(e){let t=[];t.push(e.foundation);let n=e.component;"rtp"===n?t.push(1):"rtcp"===n?t.push(2):t.push(n),t.push(e.protocol.toUpperCase()),t.push(e.priority),t.push(e.address||e.ip),t.push(e.port);let r=e.type;return t.push("typ"),t.push(r),"host"!==r&&e.relatedAddress&&e.relatedPort&&(t.push("raddr"),t.push(e.relatedAddress),t.push("rport"),t.push(e.relatedPort)),e.tcpType&&"tcp"===e.protocol.toLowerCase()&&(t.push("tcptype"),t.push(e.tcpType)),(e.usernameFragment||e.ufrag)&&(t.push("ufrag"),t.push(e.usernameFragment||e.ufrag)),"candidate:"+t.join(" ")},ey.parseIceOptions=function(e){return e.substring(14).split(" ")},ey.parseRtpMap=function(e){let t=e.substring(9).split(" "),n={payloadType:parseInt(t.shift(),10)};return t=t[0].split("/"),n.name=t[0],n.clockRate=parseInt(t[1],10),n.channels=3===t.length?parseInt(t[2],10):1,n.numChannels=n.channels,n},ey.writeRtpMap=function(e){let t=e.payloadType;void 0!==e.preferredPayloadType&&(t=e.preferredPayloadType);let n=e.channels||e.numChannels||1;return"a=rtpmap:"+t+" "+e.name+"/"+e.clockRate+(1!==n?"/"+n:"")+"\r\n"},ey.parseExtmap=function(e){let t=e.substring(9).split(" ");return{id:parseInt(t[0],10),direction:t[0].indexOf("/")>0?t[0].split("/")[1]:"sendrecv",uri:t[1],attributes:t.slice(2).join(" ")}},ey.writeExtmap=function(e){return"a=extmap:"+(e.id||e.preferredId)+(e.direction&&"sendrecv"!==e.direction?"/"+e.direction:"")+" "+e.uri+(e.attributes?" "+e.attributes:"")+"\r\n"},ey.parseFmtp=function(e){let t;let n={},r=e.substring(e.indexOf(" ")+1).split(";");for(let e=0;e<r.length;e++)n[(t=r[e].trim().split("="))[0].trim()]=t[1];return n},ey.writeFmtp=function(e){let t="",n=e.payloadType;if(void 0!==e.preferredPayloadType&&(n=e.preferredPayloadType),e.parameters&&Object.keys(e.parameters).length){let r=[];Object.keys(e.parameters).forEach(t=>{void 0!==e.parameters[t]?r.push(t+"="+e.parameters[t]):r.push(t)}),t+="a=fmtp:"+n+" "+r.join(";")+"\r\n"}return t},ey.parseRtcpFb=function(e){let t=e.substring(e.indexOf(" ")+1).split(" ");return{type:t.shift(),parameter:t.join(" ")}},ey.writeRtcpFb=function(e){let t="",n=e.payloadType;return void 0!==e.preferredPayloadType&&(n=e.preferredPayloadType),e.rtcpFeedback&&e.rtcpFeedback.length&&e.rtcpFeedback.forEach(e=>{t+="a=rtcp-fb:"+n+" "+e.type+(e.parameter&&e.parameter.length?" "+e.parameter:"")+"\r\n"}),t},ey.parseSsrcMedia=function(e){let t=e.indexOf(" "),n={ssrc:parseInt(e.substring(7,t),10)},r=e.indexOf(":",t);return r>-1?(n.attribute=e.substring(t+1,r),n.value=e.substring(r+1)):n.attribute=e.substring(t+1),n},ey.parseSsrcGroup=function(e){let t=e.substring(13).split(" ");return{semantics:t.shift(),ssrcs:t.map(e=>parseInt(e,10))}},ey.getMid=function(e){let t=ey.matchPrefix(e,"a=mid:")[0];if(t)return t.substring(6)},ey.parseFingerprint=function(e){let t=e.substring(14).split(" ");return{algorithm:t[0].toLowerCase(),value:t[1].toUpperCase()}},ey.getDtlsParameters=function(e,t){return{role:"auto",fingerprints:ey.matchPrefix(e+t,"a=fingerprint:").map(ey.parseFingerprint)}},ey.writeDtlsParameters=function(e,t){let n="a=setup:"+t+"\r\n";return e.fingerprints.forEach(e=>{n+="a=fingerprint:"+e.algorithm+" "+e.value+"\r\n"}),n},ey.parseCryptoLine=function(e){let t=e.substring(9).split(" ");return{tag:parseInt(t[0],10),cryptoSuite:t[1],keyParams:t[2],sessionParams:t.slice(3)}},ey.writeCryptoLine=function(e){return"a=crypto:"+e.tag+" "+e.cryptoSuite+" "+("object"==typeof e.keyParams?ey.writeCryptoKeyParams(e.keyParams):e.keyParams)+(e.sessionParams?" "+e.sessionParams.join(" "):"")+"\r\n"},ey.parseCryptoKeyParams=function(e){if(0!==e.indexOf("inline:"))return null;let t=e.substring(7).split("|");return{keyMethod:"inline",keySalt:t[0],lifeTime:t[1],mkiValue:t[2]?t[2].split(":")[0]:void 0,mkiLength:t[2]?t[2].split(":")[1]:void 0}},ey.writeCryptoKeyParams=function(e){return e.keyMethod+":"+e.keySalt+(e.lifeTime?"|"+e.lifeTime:"")+(e.mkiValue&&e.mkiLength?"|"+e.mkiValue+":"+e.mkiLength:"")},ey.getCryptoParameters=function(e,t){return ey.matchPrefix(e+t,"a=crypto:").map(ey.parseCryptoLine)},ey.getIceParameters=function(e,t){let n=ey.matchPrefix(e+t,"a=ice-ufrag:")[0],r=ey.matchPrefix(e+t,"a=ice-pwd:")[0];return n&&r?{usernameFragment:n.substring(12),password:r.substring(10)}:null},ey.writeIceParameters=function(e){let t="a=ice-ufrag:"+e.usernameFragment+"\r\na=ice-pwd:"+e.password+"\r\n";return e.iceLite&&(t+="a=ice-lite\r\n"),t},ey.parseRtpParameters=function(e){let t={codecs:[],headerExtensions:[],fecMechanisms:[],rtcp:[]},n=ey.splitLines(e)[0].split(" ");t.profile=n[2];for(let r=3;r<n.length;r++){let i=n[r],o=ey.matchPrefix(e,"a=rtpmap:"+i+" ")[0];if(o){let n=ey.parseRtpMap(o),r=ey.matchPrefix(e,"a=fmtp:"+i+" ");switch(n.parameters=r.length?ey.parseFmtp(r[0]):{},n.rtcpFeedback=ey.matchPrefix(e,"a=rtcp-fb:"+i+" ").map(ey.parseRtcpFb),t.codecs.push(n),n.name.toUpperCase()){case"RED":case"ULPFEC":t.fecMechanisms.push(n.name.toUpperCase())}}}ey.matchPrefix(e,"a=extmap:").forEach(e=>{t.headerExtensions.push(ey.parseExtmap(e))});let r=ey.matchPrefix(e,"a=rtcp-fb:* ").map(ey.parseRtcpFb);return t.codecs.forEach(e=>{r.forEach(t=>{e.rtcpFeedback.find(e=>e.type===t.type&&e.parameter===t.parameter)||e.rtcpFeedback.push(t)})}),t},ey.writeRtpDescription=function(e,t){let n="";n+="m="+e+" "+(t.codecs.length>0?"9":"0")+" "+(t.profile||"UDP/TLS/RTP/SAVPF")+" "+t.codecs.map(e=>void 0!==e.preferredPayloadType?e.preferredPayloadType:e.payloadType).join(" ")+"\r\nc=IN IP4 0.0.0.0\r\na=rtcp:9 IN IP4 0.0.0.0\r\n",t.codecs.forEach(e=>{n+=ey.writeRtpMap(e)+ey.writeFmtp(e)+ey.writeRtcpFb(e)});let r=0;return t.codecs.forEach(e=>{e.maxptime>r&&(r=e.maxptime)}),r>0&&(n+="a=maxptime:"+r+"\r\n"),t.headerExtensions&&t.headerExtensions.forEach(e=>{n+=ey.writeExtmap(e)}),n},ey.parseRtpEncodingParameters=function(e){let t;let n=[],r=ey.parseRtpParameters(e),i=-1!==r.fecMechanisms.indexOf("RED"),o=-1!==r.fecMechanisms.indexOf("ULPFEC"),s=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"cname"===e.attribute),a=s.length>0&&s[0].ssrc,c=ey.matchPrefix(e,"a=ssrc-group:FID").map(e=>e.substring(17).split(" ").map(e=>parseInt(e,10)));c.length>0&&c[0].length>1&&c[0][0]===a&&(t=c[0][1]),r.codecs.forEach(e=>{if("RTX"===e.name.toUpperCase()&&e.parameters.apt){let r={ssrc:a,codecPayloadType:parseInt(e.parameters.apt,10)};a&&t&&(r.rtx={ssrc:t}),n.push(r),i&&((r=JSON.parse(JSON.stringify(r))).fec={ssrc:a,mechanism:o?"red+ulpfec":"red"},n.push(r))}}),0===n.length&&a&&n.push({ssrc:a});let l=ey.matchPrefix(e,"b=");return l.length&&(l=0===l[0].indexOf("b=TIAS:")?parseInt(l[0].substring(7),10):0===l[0].indexOf("b=AS:")?950*parseInt(l[0].substring(5),10)-16e3:void 0,n.forEach(e=>{e.maxBitrate=l})),n},ey.parseRtcpParameters=function(e){let t={},n=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"cname"===e.attribute)[0];n&&(t.cname=n.value,t.ssrc=n.ssrc);let r=ey.matchPrefix(e,"a=rtcp-rsize");t.reducedSize=r.length>0,t.compound=0===r.length;let i=ey.matchPrefix(e,"a=rtcp-mux");return t.mux=i.length>0,t},ey.writeRtcpParameters=function(e){let t="";return e.reducedSize&&(t+="a=rtcp-rsize\r\n"),e.mux&&(t+="a=rtcp-mux\r\n"),void 0!==e.ssrc&&e.cname&&(t+="a=ssrc:"+e.ssrc+" cname:"+e.cname+"\r\n"),t},ey.parseMsid=function(e){let t;let n=ey.matchPrefix(e,"a=msid:");if(1===n.length)return{stream:(t=n[0].substring(7).split(" "))[0],track:t[1]};let r=ey.matchPrefix(e,"a=ssrc:").map(e=>ey.parseSsrcMedia(e)).filter(e=>"msid"===e.attribute);if(r.length>0)return{stream:(t=r[0].value.split(" "))[0],track:t[1]}},ey.parseSctpDescription=function(e){let t;let n=ey.parseMLine(e),r=ey.matchPrefix(e,"a=max-message-size:");r.length>0&&(t=parseInt(r[0].substring(19),10)),isNaN(t)&&(t=65536);let i=ey.matchPrefix(e,"a=sctp-port:");if(i.length>0)return{port:parseInt(i[0].substring(12),10),protocol:n.fmt,maxMessageSize:t};let o=ey.matchPrefix(e,"a=sctpmap:");if(o.length>0){let e=o[0].substring(10).split(" ");return{port:parseInt(e[0],10),protocol:e[1],maxMessageSize:t}}},ey.writeSctpDescription=function(e,t){let n=[];return n="DTLS/SCTP"!==e.protocol?["m="+e.kind+" 9 "+e.protocol+" "+t.protocol+"\r\n","c=IN IP4 0.0.0.0\r\n","a=sctp-port:"+t.port+"\r\n"]:["m="+e.kind+" 9 "+e.protocol+" "+t.port+"\r\n","c=IN IP4 0.0.0.0\r\n","a=sctpmap:"+t.port+" "+t.protocol+" 65535\r\n"],void 0!==t.maxMessageSize&&n.push("a=max-message-size:"+t.maxMessageSize+"\r\n"),n.join("")},ey.generateSessionId=function(){return Math.random().toString().substr(2,22)},ey.writeSessionBoilerplate=function(e,t,n){return"v=0\r\no="+(n||"thisisadapterortc")+" "+(e||ey.generateSessionId())+" "+(void 0!==t?t:2)+" IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\n"},ey.getDirection=function(e,t){let n=ey.splitLines(e);for(let e=0;e<n.length;e++)switch(n[e]){case"a=sendrecv":case"a=sendonly":case"a=recvonly":case"a=inactive":return n[e].substring(2)}return t?ey.getDirection(t):"sendrecv"},ey.getKind=function(e){return ey.splitLines(e)[0].split(" ")[0].substring(2)},ey.isRejected=function(e){return"0"===e.split(" ",2)[1]},ey.parseMLine=function(e){let t=ey.splitLines(e)[0].substring(2).split(" ");return{kind:t[0],port:parseInt(t[1],10),protocol:t[2],fmt:t.slice(3).join(" ")}},ey.parseOLine=function(e){let t=ey.matchPrefix(e,"o=")[0].substring(2).split(" ");return{username:t[0],sessionId:t[1],sessionVersion:parseInt(t[2],10),netType:t[3],addressType:t[4],address:t[5]}},ey.isValidSDP=function(e){if("string"!=typeof e||0===e.length)return!1;let t=ey.splitLines(e);for(let e=0;e<t.length;e++)if(t[e].length<2||"="!==t[e].charAt(1))return!1;return!0},eg=ey;let ew=function({window:e}={},t={shimChrome:!0,shimFirefox:!0,shimSafari:!0}){let n=function(e){let t={browser:null,version:null};if(void 0===e||!e.navigator||!e.navigator.userAgent)return t.browser="Not a browser.",t;let{navigator:n}=e;return n.mozGetUserMedia?(t.browser="firefox",t.version=p(n.userAgent,/Firefox\/(\d+)\./,1)):n.webkitGetUserMedia||!1===e.isSecureContext&&e.webkitRTCPeerConnection?(t.browser="chrome",t.version=p(n.userAgent,/Chrom(e|ium)\/(\d+)\./,2)):e.RTCPeerConnection&&n.userAgent.match(/AppleWebKit\/(\d+)\./)?(t.browser="safari",t.version=p(n.userAgent,/AppleWebKit\/(\d+)\./,1),t.supportsUnifiedPlan=e.RTCRtpTransceiver&&"currentDirection"in e.RTCRtpTransceiver.prototype):t.browser="Not a supported browser.",t}(e),r={browserDetails:n,commonShim:em,extractVersion:p,disableLog:h,disableWarnings:u,sdp:eg};switch(n.browser){case"chrome":if(!j||!j.shimPeerConnection||!t.shimChrome){f("Chrome shim is not included in this adapter release.");break}if(null===n.version){f("Chrome shim can not determine version, not shimming.");break}f("adapter.js shimming chrome."),r.browserShim=j,eT(e,n),eR(e,n),j.shimGetUserMedia(e,n),j.shimMediaStream(e,n),j.shimPeerConnection(e,n),j.shimOnTrack(e,n),j.shimAddTrackRemoveTrack(e,n),j.shimGetSendersWithDtmf(e,n),j.shimGetStats(e,n),j.shimSenderReceiverGetStats(e,n),j.fixNegotiationNeeded(e,n),e_(e,n),eC(e,n),ek(e,n),ev(e,n),eb(e,n),eS(e,n);break;case"firefox":if(!W||!W.shimPeerConnection||!t.shimFirefox){f("Firefox shim is not included in this adapter release.");break}f("adapter.js shimming firefox."),r.browserShim=W,eT(e,n),eR(e,n),W.shimGetUserMedia(e,n),W.shimPeerConnection(e,n),W.shimOnTrack(e,n),W.shimRemoveStream(e,n),W.shimSenderGetStats(e,n),W.shimReceiverGetStats(e,n),W.shimRTCDataChannel(e,n),W.shimAddTransceiver(e,n),W.shimGetParameters(e,n),W.shimCreateOffer(e,n),W.shimCreateAnswer(e,n),e_(e,n),ek(e,n),ev(e,n),eb(e,n);break;case"safari":if(!eo||!t.shimSafari){f("Safari shim is not included in this adapter release.");break}f("adapter.js shimming safari."),r.browserShim=eo,eT(e,n),eR(e,n),eo.shimRTCIceServerUrls(e,n),eo.shimCreateOfferLegacy(e,n),eo.shimCallbacksAPI(e,n),eo.shimLocalStreamsAPI(e,n),eo.shimRemoteStreamsAPI(e,n),eo.shimTrackEventTransceiver(e,n),eo.shimGetUserMedia(e,n),eo.shimAudioContext(e,n),e_(e,n),eC(e,n),ev(e,n),eb(e,n),eS(e,n);break;default:f("Unsupported browser!")}return r}({window:"undefined"==typeof window?void 0:window}),eP=ew.default||ew,eE=new class{isWebRTCSupported(){return"undefined"!=typeof RTCPeerConnection}isBrowserSupported(){let e=this.getBrowser(),t=this.getVersion();return!!this.supportedBrowsers.includes(e)&&("chrome"===e?t>=this.minChromeVersion:"firefox"===e?t>=this.minFirefoxVersion:"safari"===e&&!this.isIOS&&t>=this.minSafariVersion)}getBrowser(){return eP.browserDetails.browser}getVersion(){return eP.browserDetails.version||0}isUnifiedPlanSupported(){let e;let t=this.getBrowser(),n=eP.browserDetails.version||0;if("chrome"===t&&n<this.minChromeVersion)return!1;if("firefox"===t&&n>=this.minFirefoxVersion)return!0;if(!window.RTCRtpTransceiver||!("currentDirection"in RTCRtpTransceiver.prototype))return!1;let r=!1;try{(e=new RTCPeerConnection).addTransceiver("audio"),r=!0}catch(e){}finally{e&&e.close()}return r}toString(){return`Supports: + browser:${this.getBrowser()} + version:${this.getVersion()} + isIOS:${this.isIOS} + isWebRTCSupported:${this.isWebRTCSupported()} + isBrowserSupported:${this.isBrowserSupported()} + isUnifiedPlanSupported:${this.isUnifiedPlanSupported()}`}constructor(){this.isIOS=["iPad","iPhone","iPod"].includes(navigator.platform),this.supportedBrowsers=["firefox","chrome","safari"],this.minFirefoxVersion=59,this.minChromeVersion=72,this.minSafariVersion=605}},eD=e=>!e||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.test(e),ex=()=>Math.random().toString(36).slice(2),eI={iceServers:[{urls:"stun:stun.l.google.com:19302"},{urls:["turn:eu-0.turn.peerjs.com:3478","turn:us-0.turn.peerjs.com:3478"],username:"peerjs",credential:"peerjsp"}],sdpSemantics:"unified-plan"},eM=new class extends n{noop(){}blobToArrayBuffer(e,t){let n=new FileReader;return n.onload=function(e){e.target&&t(e.target.result)},n.readAsArrayBuffer(e),n}binaryStringToArrayBuffer(e){let t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=255&e.charCodeAt(n);return t.buffer}isSecure(){return"https:"===location.protocol}constructor(...e){super(...e),this.CLOUD_HOST="0.peerjs.com",this.CLOUD_PORT=443,this.chunkedBrowsers={Chrome:1,chrome:1},this.defaultConfig=eI,this.browser=eE.getBrowser(),this.browserVersion=eE.getVersion(),this.pack=o,this.unpack=i,this.supports=function(){let e;let t={browser:eE.isBrowserSupported(),webRTC:eE.isWebRTCSupported(),audioVideo:!1,data:!1,binaryBlob:!1,reliable:!1};if(!t.webRTC)return t;try{let n;e=new RTCPeerConnection(eI),t.audioVideo=!0;try{n=e.createDataChannel("_PEERJSTEST",{ordered:!0}),t.data=!0,t.reliable=!!n.ordered;try{n.binaryType="blob",t.binaryBlob=!eE.isIOS}catch(e){}}catch(e){}finally{n&&n.close()}}catch(e){}finally{e&&e.close()}return t}(),this.validateId=eD,this.randomToken=ex}};(_=w||(w={}))[_.Disabled=0]="Disabled",_[_.Errors=1]="Errors",_[_.Warnings=2]="Warnings",_[_.All=3]="All";var eO=new class{get logLevel(){return this._logLevel}set logLevel(e){this._logLevel=e}log(...e){this._logLevel>=3&&this._print(3,...e)}warn(...e){this._logLevel>=2&&this._print(2,...e)}error(...e){this._logLevel>=1&&this._print(1,...e)}setLogFunction(e){this._print=e}_print(e,...t){let n=["PeerJS: ",...t];for(let e in n)n[e]instanceof Error&&(n[e]="("+n[e].name+") "+n[e].message);e>=3?console.log(...n):e>=2?console.warn("WARNING",...n):e>=1&&console.error("ERROR",...n)}constructor(){this._logLevel=0}},ej={},eL=Object.prototype.hasOwnProperty,eA="~";function eB(){}function eF(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function eU(e,t,n,r,i){if("function"!=typeof n)throw TypeError("The listener must be a function");var o=new eF(n,r||e,i),s=eA?eA+t:t;return e._events[s]?e._events[s].fn?e._events[s]=[e._events[s],o]:e._events[s].push(o):(e._events[s]=o,e._eventsCount++),e}function ez(e,t){0==--e._eventsCount?e._events=new eB:delete e._events[t]}function eN(){this._events=new eB,this._eventsCount=0}Object.create&&(eB.prototype=Object.create(null),new eB().__proto__||(eA=!1)),eN.prototype.eventNames=function(){var e,t,n=[];if(0===this._eventsCount)return n;for(t in e=this._events)eL.call(e,t)&&n.push(eA?t.slice(1):t);return Object.getOwnPropertySymbols?n.concat(Object.getOwnPropertySymbols(e)):n},eN.prototype.listeners=function(e){var t=eA?eA+e:e,n=this._events[t];if(!n)return[];if(n.fn)return[n.fn];for(var r=0,i=n.length,o=Array(i);r<i;r++)o[r]=n[r].fn;return o},eN.prototype.listenerCount=function(e){var t=eA?eA+e:e,n=this._events[t];return n?n.fn?1:n.length:0},eN.prototype.emit=function(e,t,n,r,i,o){var s=eA?eA+e:e;if(!this._events[s])return!1;var a,c,l=this._events[s],p=arguments.length;if(l.fn){switch(l.once&&this.removeListener(e,l.fn,void 0,!0),p){case 1:return l.fn.call(l.context),!0;case 2:return l.fn.call(l.context,t),!0;case 3:return l.fn.call(l.context,t,n),!0;case 4:return l.fn.call(l.context,t,n,r),!0;case 5:return l.fn.call(l.context,t,n,r,i),!0;case 6:return l.fn.call(l.context,t,n,r,i,o),!0}for(c=1,a=Array(p-1);c<p;c++)a[c-1]=arguments[c];l.fn.apply(l.context,a)}else{var d,h=l.length;for(c=0;c<h;c++)switch(l[c].once&&this.removeListener(e,l[c].fn,void 0,!0),p){case 1:l[c].fn.call(l[c].context);break;case 2:l[c].fn.call(l[c].context,t);break;case 3:l[c].fn.call(l[c].context,t,n);break;case 4:l[c].fn.call(l[c].context,t,n,r);break;default:if(!a)for(d=1,a=Array(p-1);d<p;d++)a[d-1]=arguments[d];l[c].fn.apply(l[c].context,a)}}return!0},eN.prototype.on=function(e,t,n){return eU(this,e,t,n,!1)},eN.prototype.once=function(e,t,n){return eU(this,e,t,n,!0)},eN.prototype.removeListener=function(e,t,n,r){var i=eA?eA+e:e;if(!this._events[i])return this;if(!t)return ez(this,i),this;var o=this._events[i];if(o.fn)o.fn!==t||r&&!o.once||n&&o.context!==n||ez(this,i);else{for(var s=0,a=[],c=o.length;s<c;s++)(o[s].fn!==t||r&&!o[s].once||n&&o[s].context!==n)&&a.push(o[s]);a.length?this._events[i]=1===a.length?a[0]:a:ez(this,i)}return this},eN.prototype.removeAllListeners=function(e){var t;return e?(t=eA?eA+e:e,this._events[t]&&ez(this,t)):(this._events=new eB,this._eventsCount=0),this},eN.prototype.off=eN.prototype.removeListener,eN.prototype.addListener=eN.prototype.on,eN.prefixed=eA,eN.EventEmitter=eN,ej=eN,(C=P||(P={})).Data="data",C.Media="media",(v=E||(E={})).BrowserIncompatible="browser-incompatible",v.Disconnected="disconnected",v.InvalidID="invalid-id",v.InvalidKey="invalid-key",v.Network="network",v.PeerUnavailable="peer-unavailable",v.SslUnavailable="ssl-unavailable",v.ServerError="server-error",v.SocketError="socket-error",v.SocketClosed="socket-closed",v.UnavailableID="unavailable-id",v.WebRTC="webrtc",(b=D||(D={})).NegotiationFailed="negotiation-failed",b.ConnectionClosed="connection-closed",(k=x||(x={})).NotOpenYet="not-open-yet",k.MessageToBig="message-too-big",(S=I||(I={})).Binary="binary",S.BinaryUTF8="binary-utf8",S.JSON="json",S.None="raw",(T=M||(M={})).Message="message",T.Disconnected="disconnected",T.Error="error",T.Close="close",(R=O||(O={})).Heartbeat="HEARTBEAT",R.Candidate="CANDIDATE",R.Offer="OFFER",R.Answer="ANSWER",R.Open="OPEN",R.Error="ERROR",R.IdTaken="ID-TAKEN",R.InvalidKey="INVALID-KEY",R.Leave="LEAVE",R.Expire="EXPIRE";var e$={};e$=JSON.parse('{"name":"peerjs","version":"1.5.2","keywords":["peerjs","webrtc","p2p","rtc"],"description":"PeerJS client","homepage":"https://peerjs.com","bugs":{"url":"https://github.com/peers/peerjs/issues"},"repository":{"type":"git","url":"https://github.com/peers/peerjs"},"license":"MIT","contributors":["Michelle Bu <michelle@michellebu.com>","afrokick <devbyru@gmail.com>","ericz <really.ez@gmail.com>","Jairo <kidandcat@gmail.com>","Jonas Gloning <34194370+jonasgloning@users.noreply.github.com>","Jairo Caro-Accino Viciana <jairo@galax.be>","Carlos Caballero <carlos.caballero.gonzalez@gmail.com>","hc <hheennrryy@gmail.com>","Muhammad Asif <capripio@gmail.com>","PrashoonB <prashoonbhattacharjee@gmail.com>","Harsh Bardhan Mishra <47351025+HarshCasper@users.noreply.github.com>","akotynski <aleksanderkotbury@gmail.com>","lmb <i@lmb.io>","Jairooo <jairocaro@msn.com>","Moritz Stückler <moritz.stueckler@gmail.com>","Simon <crydotsnakegithub@gmail.com>","Denis Lukov <denismassters@gmail.com>","Philipp Hancke <fippo@andyet.net>","Hans Oksendahl <hansoksendahl@gmail.com>","Jess <jessachandler@gmail.com>","khankuan <khankuan@gmail.com>","DUODVK <kurmanov.work@gmail.com>","XiZhao <kwang1imsa@gmail.com>","Matthias Lohr <matthias@lohr.me>","=frank tree <=frnktrb@googlemail.com>","Andre Eckardt <aeckardt@outlook.com>","Chris Cowan <agentme49@gmail.com>","Alex Chuev <alex@chuev.com>","alxnull <alxnull@e.mail.de>","Yemel Jardi <angel.jardi@gmail.com>","Ben Parnell <benjaminparnell.94@gmail.com>","Benny Lichtner <bennlich@gmail.com>","fresheneesz <bitetrudpublic@gmail.com>","bob.barstead@exaptive.com <bob.barstead@exaptive.com>","chandika <chandika@gmail.com>","emersion <contact@emersion.fr>","Christopher Van <cvan@users.noreply.github.com>","eddieherm <edhermoso@gmail.com>","Eduardo Pinho <enet4mikeenet@gmail.com>","Evandro Zanatta <ezanatta@tray.net.br>","Gardner Bickford <gardner@users.noreply.github.com>","Gian Luca <gianluca.cecchi@cynny.com>","PatrickJS <github@gdi2290.com>","jonnyf <github@jonathanfoss.co.uk>","Hizkia Felix <hizkifw@gmail.com>","Hristo Oskov <hristo.oskov@gmail.com>","Isaac Madwed <i.madwed@gmail.com>","Ilya Konanykhin <ilya.konanykhin@gmail.com>","jasonbarry <jasbarry@me.com>","Jonathan Burke <jonathan.burke.1311@googlemail.com>","Josh Hamit <josh.hamit@gmail.com>","Jordan Austin <jrax86@gmail.com>","Joel Wetzell <jwetzell@yahoo.com>","xizhao <kevin.wang@cloudera.com>","Alberto Torres <kungfoobar@gmail.com>","Jonathan Mayol <mayoljonathan@gmail.com>","Jefferson Felix <me@jsfelix.dev>","Rolf Erik Lekang <me@rolflekang.com>","Kevin Mai-Husan Chia <mhchia@users.noreply.github.com>","Pepijn de Vos <pepijndevos@gmail.com>","JooYoung <qkdlql@naver.com>","Tobias Speicher <rootcommander@gmail.com>","Steve Blaurock <sblaurock@gmail.com>","Kyrylo Shegeda <shegeda@ualberta.ca>","Diwank Singh Tomer <singh@diwank.name>","Sören Balko <Soeren.Balko@gmail.com>","Arpit Solanki <solankiarpit1997@gmail.com>","Yuki Ito <yuki@gnnk.net>","Artur Zayats <zag2art@gmail.com>"],"funding":{"type":"opencollective","url":"https://opencollective.com/peer"},"collective":{"type":"opencollective","url":"https://opencollective.com/peer"},"files":["dist/*"],"sideEffects":["lib/global.ts","lib/supports.ts"],"main":"dist/bundler.cjs","module":"dist/bundler.mjs","browser-minified":"dist/peerjs.min.js","browser-unminified":"dist/peerjs.js","browser-minified-cbor":"dist/serializer.cbor.mjs","browser-minified-msgpack":"dist/serializer.msgpack.mjs","types":"dist/types.d.ts","engines":{"node":">= 14"},"targets":{"types":{"source":"lib/exports.ts"},"main":{"source":"lib/exports.ts","sourceMap":{"inlineSources":true}},"module":{"source":"lib/exports.ts","includeNodeModules":["eventemitter3"],"sourceMap":{"inlineSources":true}},"browser-minified":{"context":"browser","outputFormat":"global","optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 80, safari >= 15"},"source":"lib/global.ts"},"browser-unminified":{"context":"browser","outputFormat":"global","optimize":false,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 80, safari >= 15"},"source":"lib/global.ts"},"browser-minified-cbor":{"context":"browser","outputFormat":"esmodule","isLibrary":true,"optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 102, safari >= 15"},"source":"lib/dataconnection/StreamConnection/Cbor.ts"},"browser-minified-msgpack":{"context":"browser","outputFormat":"esmodule","isLibrary":true,"optimize":true,"engines":{"browsers":"chrome >= 83, edge >= 83, firefox >= 102, safari >= 15"},"source":"lib/dataconnection/StreamConnection/MsgPack.ts"}},"scripts":{"contributors":"git-authors-cli --print=false && prettier --write package.json && git add package.json package-lock.json && git commit -m \\"chore(contributors): update and sort contributors list\\"","check":"tsc --noEmit && tsc -p e2e/tsconfig.json --noEmit","watch":"parcel watch","build":"rm -rf dist && parcel build","prepublishOnly":"npm run build","test":"jest","test:watch":"jest --watch","coverage":"jest --coverage --collectCoverageFrom=\\"./lib/**\\"","format":"prettier --write .","format:check":"prettier --check .","semantic-release":"semantic-release","e2e":"wdio run e2e/wdio.local.conf.ts","e2e:bstack":"wdio run e2e/wdio.bstack.conf.ts"},"devDependencies":{"@parcel/config-default":"^2.9.3","@parcel/packager-ts":"^2.9.3","@parcel/transformer-typescript-tsc":"^2.9.3","@parcel/transformer-typescript-types":"^2.9.3","@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@swc/core":"^1.3.27","@swc/jest":"^0.2.24","@types/jasmine":"^4.3.4","@wdio/browserstack-service":"^8.11.2","@wdio/cli":"^8.11.2","@wdio/globals":"^8.11.2","@wdio/jasmine-framework":"^8.11.2","@wdio/local-runner":"^8.11.2","@wdio/spec-reporter":"^8.11.2","@wdio/types":"^8.10.4","http-server":"^14.1.1","jest":"^29.3.1","jest-environment-jsdom":"^29.3.1","mock-socket":"^9.0.0","parcel":"^2.9.3","prettier":"^3.0.0","semantic-release":"^21.0.0","ts-node":"^10.9.1","typescript":"^5.0.0","wdio-geckodriver-service":"^5.0.1"},"dependencies":{"@msgpack/msgpack":"^2.8.0","cbor-x":"1.5.4","eventemitter3":"^4.0.7","peerjs-js-binarypack":"^2.1.0","webrtc-adapter":"^8.0.0"},"alias":{"process":false,"buffer":false}}');class eJ extends ej.EventEmitter{start(e,t){this._id=e;let n=`${this._baseUrl}&id=${e}&token=${t}`;!this._socket&&this._disconnected&&(this._socket=new WebSocket(n+"&version="+e$.version),this._disconnected=!1,this._socket.onmessage=e=>{let t;try{t=JSON.parse(e.data),eO.log("Server message received:",t)}catch(t){eO.log("Invalid server message",e.data);return}this.emit(M.Message,t)},this._socket.onclose=e=>{this._disconnected||(eO.log("Socket closed.",e),this._cleanup(),this._disconnected=!0,this.emit(M.Disconnected))},this._socket.onopen=()=>{this._disconnected||(this._sendQueuedMessages(),eO.log("Socket open"),this._scheduleHeartbeat())})}_scheduleHeartbeat(){this._wsPingTimer=setTimeout(()=>{this._sendHeartbeat()},this.pingInterval)}_sendHeartbeat(){if(!this._wsOpen()){eO.log("Cannot send heartbeat, because socket closed");return}let e=JSON.stringify({type:O.Heartbeat});this._socket.send(e),this._scheduleHeartbeat()}_wsOpen(){return!!this._socket&&1===this._socket.readyState}_sendQueuedMessages(){let e=[...this._messagesQueue];for(let t of(this._messagesQueue=[],e))this.send(t)}send(e){if(this._disconnected)return;if(!this._id){this._messagesQueue.push(e);return}if(!e.type){this.emit(M.Error,"Invalid message");return}if(!this._wsOpen())return;let t=JSON.stringify(e);this._socket.send(t)}close(){this._disconnected||(this._cleanup(),this._disconnected=!0)}_cleanup(){this._socket&&(this._socket.onopen=this._socket.onmessage=this._socket.onclose=null,this._socket.close(),this._socket=void 0),clearTimeout(this._wsPingTimer)}constructor(e,t,n,r,i,o=5e3){super(),this.pingInterval=o,this._disconnected=!0,this._messagesQueue=[],this._baseUrl=(e?"wss://":"ws://")+t+":"+n+r+"peerjs?key="+i}}class eV{startConnection(e){let t=this._startPeerConnection();if(this.connection.peerConnection=t,this.connection.type===P.Media&&e._stream&&this._addTracksToConnection(e._stream,t),e.originator){let n=this.connection,r={ordered:!!e.reliable},i=t.createDataChannel(n.label,r);n._initializeDataChannel(i),this._makeOffer()}else this.handleSDP("OFFER",e.sdp)}_startPeerConnection(){eO.log("Creating RTCPeerConnection.");let e=new RTCPeerConnection(this.connection.provider.options.config);return this._setupListeners(e),e}_setupListeners(e){let t=this.connection.peer,n=this.connection.connectionId,r=this.connection.type,i=this.connection.provider;eO.log("Listening for ICE candidates."),e.onicecandidate=e=>{e.candidate&&e.candidate.candidate&&(eO.log(`Received ICE candidates for ${t}:`,e.candidate),i.socket.send({type:O.Candidate,payload:{candidate:e.candidate,type:r,connectionId:n},dst:t}))},e.oniceconnectionstatechange=()=>{switch(e.iceConnectionState){case"failed":eO.log("iceConnectionState is failed, closing connections to "+t),this.connection.emitError(D.NegotiationFailed,"Negotiation of connection to "+t+" failed."),this.connection.close();break;case"closed":eO.log("iceConnectionState is closed, closing connections to "+t),this.connection.emitError(D.ConnectionClosed,"Connection to "+t+" closed."),this.connection.close();break;case"disconnected":eO.log("iceConnectionState changed to disconnected on the connection with "+t);break;case"completed":e.onicecandidate=()=>{}}this.connection.emit("iceStateChanged",e.iceConnectionState)},eO.log("Listening for data channel"),e.ondatachannel=e=>{eO.log("Received data channel");let r=e.channel;i.getConnection(t,n)._initializeDataChannel(r)},eO.log("Listening for remote stream"),e.ontrack=e=>{eO.log("Received remote stream");let r=e.streams[0],o=i.getConnection(t,n);o.type===P.Media&&this._addStreamToMediaConnection(r,o)}}cleanup(){eO.log("Cleaning up PeerConnection to "+this.connection.peer);let e=this.connection.peerConnection;if(!e)return;this.connection.peerConnection=null,e.onicecandidate=e.oniceconnectionstatechange=e.ondatachannel=e.ontrack=()=>{};let t="closed"!==e.signalingState,n=!1,r=this.connection.dataChannel;r&&(n=!!r.readyState&&"closed"!==r.readyState),(t||n)&&e.close()}async _makeOffer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createOffer(this.connection.options.constraints);eO.log("Created offer."),this.connection.options.sdpTransform&&"function"==typeof this.connection.options.sdpTransform&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp);try{await e.setLocalDescription(n),eO.log("Set localDescription:",n,`for:${this.connection.peer}`);let r={sdp:n,type:this.connection.type,connectionId:this.connection.connectionId,metadata:this.connection.metadata};if(this.connection.type===P.Data){let e=this.connection;r={...r,label:e.label,reliable:e.reliable,serialization:e.serialization}}t.socket.send({type:O.Offer,payload:r,dst:this.connection.peer})}catch(e){"OperationError: Failed to set local offer sdp: Called in wrong state: kHaveRemoteOffer"!=e&&(t.emitError(E.WebRTC,e),eO.log("Failed to setLocalDescription, ",e))}}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to createOffer, ",e)}}async _makeAnswer(){let e=this.connection.peerConnection,t=this.connection.provider;try{let n=await e.createAnswer();eO.log("Created answer."),this.connection.options.sdpTransform&&"function"==typeof this.connection.options.sdpTransform&&(n.sdp=this.connection.options.sdpTransform(n.sdp)||n.sdp);try{await e.setLocalDescription(n),eO.log("Set localDescription:",n,`for:${this.connection.peer}`),t.socket.send({type:O.Answer,payload:{sdp:n,type:this.connection.type,connectionId:this.connection.connectionId},dst:this.connection.peer})}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to setLocalDescription, ",e)}}catch(e){t.emitError(E.WebRTC,e),eO.log("Failed to create answer, ",e)}}async handleSDP(e,t){t=new RTCSessionDescription(t);let n=this.connection.peerConnection,r=this.connection.provider;eO.log("Setting remote description",t);try{await n.setRemoteDescription(t),eO.log(`Set remoteDescription:${e} for:${this.connection.peer}`),"OFFER"===e&&await this._makeAnswer()}catch(e){r.emitError(E.WebRTC,e),eO.log("Failed to setRemoteDescription, ",e)}}async handleCandidate(e){eO.log("handleCandidate:",e);try{await this.connection.peerConnection.addIceCandidate(e),eO.log(`Added ICE candidate for:${this.connection.peer}`)}catch(e){this.connection.provider.emitError(E.WebRTC,e),eO.log("Failed to handleCandidate, ",e)}}_addTracksToConnection(e,t){if(eO.log(`add tracks from stream ${e.id} to peer connection`),!t.addTrack)return eO.error("Your browser does't support RTCPeerConnection#addTrack. Ignored.");e.getTracks().forEach(n=>{t.addTrack(n,e)})}_addStreamToMediaConnection(e,t){eO.log(`add stream ${e.id} to media connection ${t.connectionId}`),t.addStream(e)}constructor(e){this.connection=e}}class eG extends ej.EventEmitter{emitError(e,t){eO.error("Error:",t),this.emit("error",new eW(`${e}`,t))}}class eW extends Error{constructor(e,t){"string"==typeof t?super(t):(super(),Object.assign(this,t)),this.type=e}}class eH extends eG{get open(){return this._open}constructor(e,t,n){super(),this.peer=e,this.provider=t,this.options=n,this._open=!1,this.metadata=n.metadata}}class eY extends eH{get type(){return P.Media}get localStream(){return this._localStream}get remoteStream(){return this._remoteStream}_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{eO.log(`DC#${this.connectionId} dc connection success`),this.emit("willCloseOnRemote")},this.dataChannel.onclose=()=>{eO.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}addStream(e){eO.log("Receiving stream",e),this._remoteStream=e,super.emit("stream",e)}handleMessage(e){let t=e.type,n=e.payload;switch(e.type){case O.Answer:this._negotiator.handleSDP(t,n.sdp),this._open=!0;break;case O.Candidate:this._negotiator.handleCandidate(n.candidate);break;default:eO.warn(`Unrecognized message type:${t} from peer:${this.peer}`)}}answer(e,t={}){if(this._localStream){eO.warn("Local stream already exists on this MediaConnection. Are you answering a call twice?");return}for(let n of(this._localStream=e,t&&t.sdpTransform&&(this.options.sdpTransform=t.sdpTransform),this._negotiator.startConnection({...this.options._payload,_stream:e}),this.provider._getMessages(this.connectionId)))this.handleMessage(n);this._open=!0}close(){this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this._localStream=null,this._remoteStream=null,this.provider&&(this.provider._removeConnection(this),this.provider=null),this.options&&this.options._stream&&(this.options._stream=null),this.open&&(this._open=!1,super.emit("close"))}constructor(e,t,n){super(e,t,n),this._localStream=this.options._stream,this.connectionId=this.options.connectionId||eY.ID_PREFIX+eM.randomToken(),this._negotiator=new eV(this),this._localStream&&this._negotiator.startConnection({_stream:this._localStream,originator:!0})}}eY.ID_PREFIX="mc_";class eK{_buildRequest(e){let t=this._options.secure?"https":"http",{host:n,port:r,path:i,key:o}=this._options,s=new URL(`${t}://${n}:${r}${i}${o}/${e}`);return s.searchParams.set("ts",`${Date.now()}${Math.random()}`),s.searchParams.set("version",e$.version),fetch(s.href,{referrerPolicy:this._options.referrerPolicy})}async retrieveId(){try{let e=await this._buildRequest("id");if(200!==e.status)throw Error(`Error. Status:${e.status}`);return e.text()}catch(t){eO.error("Error retrieving ID",t);let e="";throw"/"===this._options.path&&this._options.host!==eM.CLOUD_HOST&&(e=" If you passed in a `path` to your self-hosted PeerServer, you'll also need to pass in that same path when creating a new Peer."),Error("Could not get an ID from the server."+e)}}async listAllPeers(){try{let e=await this._buildRequest("peers");if(200!==e.status){if(401===e.status){let e="";throw e=this._options.host===eM.CLOUD_HOST?"It looks like you're using the cloud server. You can email team@peerjs.com to enable peer listing for your API key.":"You need to enable `allow_discovery` on your self-hosted PeerServer to use this feature.",Error("It doesn't look like you have permission to list peers IDs. "+e)}throw Error(`Error. Status:${e.status}`)}return e.json()}catch(e){throw eO.error("Error retrieving list peers",e),Error("Could not get list peers from the server."+e)}}constructor(e){this._options=e}}class eX extends eH{get type(){return P.Data}_initializeDataChannel(e){this.dataChannel=e,this.dataChannel.onopen=()=>{eO.log(`DC#${this.connectionId} dc connection success`),this._open=!0,this.emit("open")},this.dataChannel.onmessage=e=>{eO.log(`DC#${this.connectionId} dc onmessage:`,e.data)},this.dataChannel.onclose=()=>{eO.log(`DC#${this.connectionId} dc closed for:`,this.peer),this.close()}}close(e){if(e?.flush){this.send({__peerData:{type:"close"}});return}this._negotiator&&(this._negotiator.cleanup(),this._negotiator=null),this.provider&&(this.provider._removeConnection(this),this.provider=null),this.dataChannel&&(this.dataChannel.onopen=null,this.dataChannel.onmessage=null,this.dataChannel.onclose=null,this.dataChannel=null),this.open&&(this._open=!1,super.emit("close"))}send(e,t=!1){if(!this.open){this.emitError(x.NotOpenYet,"Connection is not open. You should listen for the `open` event before sending messages.");return}return this._send(e,t)}async handleMessage(e){let t=e.payload;switch(e.type){case O.Answer:await this._negotiator.handleSDP(e.type,t.sdp);break;case O.Candidate:await this._negotiator.handleCandidate(t.candidate);break;default:eO.warn("Unrecognized message type:",e.type,"from peer:",this.peer)}}constructor(e,t,n){super(e,t,n),this.connectionId=this.options.connectionId||eX.ID_PREFIX+ex(),this.label=this.options.label||this.connectionId,this.reliable=!!this.options.reliable,this._negotiator=new eV(this),this._negotiator.startConnection(this.options._payload||{originator:!0,reliable:this.reliable})}}eX.ID_PREFIX="dc_",eX.MAX_BUFFERED_AMOUNT=8388608;class eq extends eX{get bufferSize(){return this._bufferSize}_initializeDataChannel(e){super._initializeDataChannel(e),this.dataChannel.binaryType="arraybuffer",this.dataChannel.addEventListener("message",e=>this._handleDataMessage(e))}_bufferedSend(e){(this._buffering||!this._trySend(e))&&(this._buffer.push(e),this._bufferSize=this._buffer.length)}_trySend(e){if(!this.open)return!1;if(this.dataChannel.bufferedAmount>eX.MAX_BUFFERED_AMOUNT)return this._buffering=!0,setTimeout(()=>{this._buffering=!1,this._tryBuffer()},50),!1;try{this.dataChannel.send(e)}catch(e){return eO.error(`DC#:${this.connectionId} Error when sending:`,e),this._buffering=!0,this.close(),!1}return!0}_tryBuffer(){if(!this.open||0===this._buffer.length)return;let e=this._buffer[0];this._trySend(e)&&(this._buffer.shift(),this._bufferSize=this._buffer.length,this._tryBuffer())}close(e){if(e?.flush){this.send({__peerData:{type:"close"}});return}this._buffer=[],this._bufferSize=0,super.close()}constructor(...e){super(...e),this._buffer=[],this._bufferSize=0,this._buffering=!1}}class eQ extends eq{close(e){super.close(e),this._chunkedData={}}_handleDataMessage({data:e}){let t=i(e),n=t.__peerData;if(n){if("close"===n.type){this.close();return}this._handleChunk(t);return}this.emit("data",t)}_handleChunk(e){let t=e.__peerData,n=this._chunkedData[t]||{data:[],count:0,total:e.total};if(n.data[e.n]=new Uint8Array(e.data),n.count++,this._chunkedData[t]=n,n.total===n.count){delete this._chunkedData[t];let e=function(e){let t=0;for(let n of e)t+=n.byteLength;let n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.byteLength;return n}(n.data);this._handleDataMessage({data:e})}}_send(e,t){let n=o(e);if(n instanceof Promise)return this._send_blob(n);if(!t&&n.byteLength>this.chunker.chunkedMTU){this._sendChunks(n);return}this._bufferedSend(n)}async _send_blob(e){let t=await e;if(t.byteLength>this.chunker.chunkedMTU){this._sendChunks(t);return}this._bufferedSend(t)}_sendChunks(e){let t=this.chunker.chunk(e);for(let e of(eO.log(`DC#${this.connectionId} Try to send ${t.length} chunks...`),t))this.send(e,!0)}constructor(e,t,r){super(e,t,r),this.chunker=new n,this.serialization=I.Binary,this._chunkedData={}}}class eZ extends eq{_handleDataMessage({data:e}){super.emit("data",e)}_send(e,t){this._bufferedSend(e)}constructor(...e){super(...e),this.serialization=I.None}}class e0 extends eq{_handleDataMessage({data:e}){let t=this.parse(this.decoder.decode(e)),n=t.__peerData;if(n&&"close"===n.type){this.close();return}this.emit("data",t)}_send(e,t){let n=this.encoder.encode(this.stringify(e));if(n.byteLength>=eM.chunkedMTU){this.emitError(x.MessageToBig,"Message too big for JSON channel");return}this._bufferedSend(n)}constructor(...e){super(...e),this.serialization=I.JSON,this.encoder=new TextEncoder,this.decoder=new TextDecoder,this.stringify=JSON.stringify,this.parse=JSON.parse}}class e1 extends eG{get id(){return this._id}get options(){return this._options}get open(){return this._open}get socket(){return this._socket}get connections(){let e=Object.create(null);for(let[t,n]of this._connections)e[t]=n;return e}get destroyed(){return this._destroyed}get disconnected(){return this._disconnected}_createServerConnection(){let e=new eJ(this._options.secure,this._options.host,this._options.port,this._options.path,this._options.key,this._options.pingInterval);return e.on(M.Message,e=>{this._handleMessage(e)}),e.on(M.Error,e=>{this._abort(E.SocketError,e)}),e.on(M.Disconnected,()=>{this.disconnected||(this.emitError(E.Network,"Lost connection to server."),this.disconnect())}),e.on(M.Close,()=>{this.disconnected||this._abort(E.SocketClosed,"Underlying socket is already closed.")}),e}_initialize(e){this._id=e,this.socket.start(e,this._options.token)}_handleMessage(e){let t=e.type,n=e.payload,r=e.src;switch(t){case O.Open:this._lastServerId=this.id,this._open=!0,this.emit("open",this.id);break;case O.Error:this._abort(E.ServerError,n.msg);break;case O.IdTaken:this._abort(E.UnavailableID,`ID "${this.id}" is taken`);break;case O.InvalidKey:this._abort(E.InvalidKey,`API KEY "${this._options.key}" is invalid`);break;case O.Leave:eO.log(`Received leave message from ${r}`),this._cleanupPeer(r),this._connections.delete(r);break;case O.Expire:this.emitError(E.PeerUnavailable,`Could not connect to peer ${r}`);break;case O.Offer:{let e=n.connectionId,t=this.getConnection(r,e);if(t&&(t.close(),eO.warn(`Offer received for existing Connection ID:${e}`)),n.type===P.Media){let i=new eY(r,this,{connectionId:e,_payload:n,metadata:n.metadata});t=i,this._addConnection(r,t),this.emit("call",i)}else if(n.type===P.Data){let i=new this._serializers[n.serialization](r,this,{connectionId:e,_payload:n,metadata:n.metadata,label:n.label,serialization:n.serialization,reliable:n.reliable});t=i,this._addConnection(r,t),this.emit("connection",i)}else{eO.warn(`Received malformed connection type:${n.type}`);return}for(let n of this._getMessages(e))t.handleMessage(n);break}default:{if(!n){eO.warn(`You received a malformed message from ${r} of type ${t}`);return}let i=n.connectionId,o=this.getConnection(r,i);o&&o.peerConnection?o.handleMessage(e):i?this._storeMessage(i,e):eO.warn("You received an unrecognized message:",e)}}}_storeMessage(e,t){this._lostMessages.has(e)||this._lostMessages.set(e,[]),this._lostMessages.get(e).push(t)}_getMessages(e){let t=this._lostMessages.get(e);return t?(this._lostMessages.delete(e),t):[]}connect(e,t={}){if(t={serialization:"default",...t},this.disconnected){eO.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect, or call reconnect on this peer if you believe its ID to still be available."),this.emitError(E.Disconnected,"Cannot connect to new Peer after disconnecting from server.");return}let n=new this._serializers[t.serialization](e,this,t);return this._addConnection(e,n),n}call(e,t,n={}){if(this.disconnected){eO.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect."),this.emitError(E.Disconnected,"Cannot connect to new Peer after disconnecting from server.");return}if(!t){eO.error("To call a peer, you must provide a stream from your browser's `getUserMedia`.");return}let r=new eY(e,this,{...n,_stream:t});return this._addConnection(e,r),r}_addConnection(e,t){eO.log(`add connection ${t.type}:${t.connectionId} to peerId:${e}`),this._connections.has(e)||this._connections.set(e,[]),this._connections.get(e).push(t)}_removeConnection(e){let t=this._connections.get(e.peer);if(t){let n=t.indexOf(e);-1!==n&&t.splice(n,1)}this._lostMessages.delete(e.connectionId)}getConnection(e,t){let n=this._connections.get(e);if(!n)return null;for(let e of n)if(e.connectionId===t)return e;return null}_delayedAbort(e,t){setTimeout(()=>{this._abort(e,t)},0)}_abort(e,t){eO.error("Aborting!"),this.emitError(e,t),this._lastServerId?this.disconnect():this.destroy()}destroy(){this.destroyed||(eO.log(`Destroy peer with ID:${this.id}`),this.disconnect(),this._cleanup(),this._destroyed=!0,this.emit("close"))}_cleanup(){for(let e of this._connections.keys())this._cleanupPeer(e),this._connections.delete(e);this.socket.removeAllListeners()}_cleanupPeer(e){let t=this._connections.get(e);if(t)for(let e of t)e.close()}disconnect(){if(this.disconnected)return;let e=this.id;eO.log(`Disconnect peer with ID:${e}`),this._disconnected=!0,this._open=!1,this.socket.close(),this._lastServerId=e,this._id=null,this.emit("disconnected",e)}reconnect(){if(this.disconnected&&!this.destroyed)eO.log(`Attempting reconnection to server with ID ${this._lastServerId}`),this._disconnected=!1,this._initialize(this._lastServerId);else if(this.destroyed)throw Error("This peer cannot reconnect to the server. It has already been destroyed.");else if(this.disconnected||this.open)throw Error(`Peer ${this.id} cannot reconnect because it is not disconnected from the server!`);else eO.error("In a hurry? We're still trying to make the initial connection!")}listAllPeers(e=e=>{}){this._api.listAllPeers().then(t=>e(t)).catch(e=>this._abort(E.ServerError,e))}constructor(e,t){let n;if(super(),this._serializers={raw:eZ,json:e0,binary:eQ,"binary-utf8":eQ,default:eQ},this._id=null,this._lastServerId=null,this._destroyed=!1,this._disconnected=!1,this._open=!1,this._connections=new Map,this._lostMessages=new Map,e&&e.constructor==Object?t=e:e&&(n=e.toString()),t={debug:0,host:eM.CLOUD_HOST,port:eM.CLOUD_PORT,path:"/",key:e1.DEFAULT_KEY,token:eM.randomToken(),config:eM.defaultConfig,referrerPolicy:"strict-origin-when-cross-origin",serializers:{},...t},this._options=t,this._serializers={...this._serializers,...this.options.serializers},"/"===this._options.host&&(this._options.host=window.location.hostname),this._options.path&&("/"!==this._options.path[0]&&(this._options.path="/"+this._options.path),"/"!==this._options.path[this._options.path.length-1]&&(this._options.path+="/")),void 0===this._options.secure&&this._options.host!==eM.CLOUD_HOST?this._options.secure=eM.isSecure():this._options.host==eM.CLOUD_HOST&&(this._options.secure=!0),this._options.logFunction&&eO.setLogFunction(this._options.logFunction),eO.logLevel=this._options.debug||0,this._api=new eK(t),this._socket=this._createServerConnection(),!eM.supports.audioVideo&&!eM.supports.data){this._delayedAbort(E.BrowserIncompatible,"The current browser does not support WebRTC");return}if(n&&!eM.validateId(n)){this._delayedAbort(E.InvalidID,`ID "${n}" is invalid`);return}n?this._initialize(n):this._api.retrieveId().then(e=>this._initialize(e)).catch(e=>this._abort(E.ServerError,e))}}e1.DEFAULT_KEY="peerjs",window.peerjs={Peer:e1,util:eM},window.Peer=e1})(); +//# sourceMappingURL=peerjs.min.js.map diff --git a/src/js/joined.js b/src/js/joined.js new file mode 100644 index 0000000..0011625 --- /dev/null +++ b/src/js/joined.js @@ -0,0 +1,48 @@ +var s = document.createElement('script'); +s.setAttribute('src','https://unpkg.com/peerjs@1.5.2/dist/peerjs.min.js'); +document.body.appendChild(s); +CLIENT = {}; +CLIENT["message_queue"] = []; +CLIENT["open"] = false; +CLIENT.lobby_id = null; +CLIENT["join"] = function(id) { + if(CLIENT.lobby_id != null){ + console.log("Somehow called .join() twice") + return; + } + CLIENT.lobby_id = id; + console.log("lobby_id:" + CLIENT.lobby_id); + var peer = new Peer(); + peer.on("open",function(){ + CLIENT.peer = peer; + var conn = CLIENT.peer.connect("ANGRY_ADVENTURE_" + CLIENT.lobby_id); + CLIENT.conn = conn + console.log("conn is:"); + console.log(conn); + conn.on("open",function(){ + console.log("Opened peer"); + CLIENT.open = true + conn.on("data",function(data) { + console.log("Got data:" + data); + CLIENT.message_queue.push(data); + }) + }); + + }) + peer.on('error',function(err){ + console.log("Error on peer:"); + console.log(err); + CLIENT.error = err; + }); +}; +CLIENT.send = function(data) { + CLIENT.conn.send(data); +}; +CLIENT.get = function(){ + if(CLIENT.message_queue.length >= 1){ + return CLIENT.message_queue.shift(); + }else{ + return null; + } +} +true; diff --git a/src/js/lobby.js b/src/js/lobby.js new file mode 100644 index 0000000..e012b92 --- /dev/null +++ b/src/js/lobby.js @@ -0,0 +1,80 @@ +var s = document.createElement('script'); +s.setAttribute('src','https://unpkg.com/peerjs@1.5.2/dist/peerjs.min.js'); +document.body.appendChild(s); +function genRanHex(size) { + return Array.apply(null,Array(size)).map(() => Math.floor(Math.random() * 16).toString(16)).join(''); +} +GLOBAL = {} +GLOBAL.lobby_id = genRanHex(6); +console.log("lobby_id:" + GLOBAL.lobby_id); +var peer = new Peer("ANGRY_ADVENTURE_" + GLOBAL.lobby_id, {"debug": 3}); +var peer_id = null; +var poo = peer.on('open', function(id) { + console.log('My peer ID is: ' + id); + peer.on("connection",function(conn){ + console.log("Got a connection!") + GLOBAL.connections[conn.peer] = conn + conn.send("Hello!") + GLOBAL.message_queue.push({ + "msg": "data", + "peer": conn.peer, + "data": '{"msg":"player_joined"}' + }) + conn.on("data",function(data){ + console.log("Got some data from a peer!:" + data) + GLOBAL.message_queue.push({ + "msg": "data", + "peer": conn.peer, + "data": data + }) + }) + conn.on("error",function(err){ + console.log("Error on a connection:" + err) + GLOBAL.message_queue.push({ + "msg": "error", + "peer": conn.peer, + "err": err + }) + }) + }) + GLOBAL.peer_id = id; +}); +console.log("poo:" + poo); +console.log("Peer:",peer) +var poe = peer.on('error',function(err){ + console.log("Error on peer:") + console.log(err); + GLOBAL.error = err; +}); +GLOBAL.peer = peer; +GLOBAL.peer_id = () => peer_id; +GLOBAL.get_error = () => error; +GLOBAL.connections = {}; +GLOBAL["get_message"] = function(){ + if(GLOBAL.message_queue.length >= 1){ + return GLOBAL.message_queue.shift(); + }else{ + return null; + } +} +GLOBAL["send_message"] = function(who, data){ + if(GLOBAL.connections[who]._open){ + GLOBAL.connections[who].send(data); + return true + } + return false; +} +GLOBAL["broadcast"] = function(data){ + console.log("Server broadcasting message:") + console.log(data) + for(var peerid in GLOBAL.connections){ + if (GLOBAL.connections[peerid]._open){ + GLOBAL.connections[peerid].send(data); + } + } + return true; +} +GLOBAL["get_peers"] = function() { + return GLOBAL.connections; +} +GLOBAL.message_queue = []; diff --git a/src/lobby_menu.moon b/src/lobby_menu.moon new file mode 100644 index 0000000..23081dc --- /dev/null +++ b/src/lobby_menu.moon @@ -0,0 +1,80 @@ +main = require "main" +color = require "color" +ui = require "ui" +world = require "world" +char = require "char" +player = require "player" +bp = require "broadphase" +action = require "action" +room = require "room" +import World from world +import LocalPlayer from player +import LobbyRoom from room + +mod = ... + +mod.node = am.group! +--mod.node\append(am.sprite("data/lobby_bg.png")) +mod.node\append(am.translate(0,main.height/2)^ am.text("Join lobby...", color.fg, "center","top")\tag("join_id")) +char_selector = am.translate(0,(main.height/2)+64) +mod.loaded = false +mod.load = (id) -> + main.world = World! + main.world\set_room(LobbyRoom!) + main.world\join(id) + mod.node("join_id").text = "Lobby id:" .. id + main.root("screen")\append(mod.node) + touch_indicator = am.group! + touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") + touch_loc = am.translate(0,0)^ touch_cursor + touch_indicator\append(touch_loc) + char_selector = ui.create_char_selector2(mod.node) + char_right, char_left, char_text = char_selector[1], char_selector[2], char_selector[3] + mod.buttons = {char_left, char_right} + classes = char.class_order + class_s = 1 + char_right.color = color.transparent + char_left.color = color.transparent + mod.node\action(coroutine.create(() -> + while not main.world.client_open! + coroutine.yield! + char_left.color = color.white + char_right.color = color.white + char_text.text = "Tumbler" + main.world\load! + action.sync_players! + while true + if #main.win\active_touches! > 0 + touch = main.win\touch_position(1) + touch_cursor.color = vec4(1,1,1,1) + touch_loc.x = touch.x + touch_loc.y = touch.y + col_but = bp.check(touch.x, touch.y + 64) + if #col_but > 0 + touch_cursor.color = vec4(0.5,0.5,0.5,0.5) + if #col_but > 0 and main.win\touch_began(1) + if col_but[1] == char_left + class_s = class_s - 1 + if class_s == 0 + class_s = #classes + if col_but[1] == char_right + class_s = class_s + 1 + if class_s > #classes + class_s = 1 + char_text.text = classes[class_s] + action.request_class_change(classes[class_s]) + else + touch_cursor.color = vec4(0,0,0,0) + coroutine.yield! + + )) + mod.node\append(touch_indicator) + mod.loaded = true + +mod.unload = () -> + main.root("screen")\remove(mod.node) + for _, button in pairs(mod.buttons) + bp.remove(button) + mod.loaded = false + +mod diff --git a/src/main.moon b/src/main.moon new file mode 100644 index 0000000..fca600f --- /dev/null +++ b/src/main.moon @@ -0,0 +1,72 @@ +mod = ... + +require "ext" +mod.width = 640 +mod.height = 480 +win = am.window{ + title: "Fools Rush In" + width: mod.width + height: mod.height + clear_color: vec4(85/255,180/255,255/255,255/255) +} +mod.root = am.group() +mod.pips = 5 +char = require "char" +player = require "player" +import LocalPlayer from player +util = require "util" +world = require "world" +import World from world +main_menu = require "main_menu" +rng_seed = am.eval_js("Math.random()") +math.randomseed(rng_seed*1000) + +mod.screenpos = util.Vec2! + +mod.reload = () -> + --[[Groups for different layers]] + mod.screen = am.group!\tag("screen")^ am.translate(0,0)--Things that should draw on top of everything else, like a hud + mod.world_close = am.group!\tag("close")^ am.translate(0,0)--Things that should draw "close to the camera", fast-parallax background + mod.world = am.translate(0,0) ^ am.group({ + am.group!\tag("world_front"), -- things in front of players + am.group!\tag("world_characters"), -- players + am.group!\tag("world_behind"), -- things behind players + })\tag("world")--Characters, the world, players, ect. + mod.world_far = am.group!\tag("far")^ am.translate(0,0) --Things that move slowly in the background + mod.background = am.group!\tag("bg")--The background does not move, draws behind everything. + mod.root = am.group{ + mod.background, + mod.world_far, + mod.world, + mod.world_close, + mod.screen, + } + win.scene = mod.root + mod.action_queue = { + {main_menu.load, {}} + } + + mod.root\action(coroutine.create(() -> + while true + if mod.server + mod.server\update! + coroutine.yield! + )) + mod.root\action(coroutine.create(() -> + while true + start_time = am.current_time! + mod.world.x = -mod.screenpos.x + mod.world.y = -mod.screenpos.y + if #mod.action_queue > 0 + action = table.remove(mod.action_queue) + f, args = action[1], action[2] + f(unpack(args)) + if mod.world + mod.world\update! + end_time = am.current_time! + + coroutine.yield! + )) +mod.reload! +mod.win = win +mod diff --git a/src/main_menu.moon b/src/main_menu.moon new file mode 100644 index 0000000..fee6fb5 --- /dev/null +++ b/src/main_menu.moon @@ -0,0 +1,81 @@ + +main = require "main" +bp = require "broadphase" +color = require "color" +ui = require "ui" + +world = require "world" --delete when done +import Server from world +import World from world +mod = ... + +-- 4 parts? +nwidth = 6 +section_width = 32 +start_x = (-32)*(nwidth/2) +mod.node = am.group! +mod.node\append(am.sprite("data/main_bg.png")) +start_y = 0 +padding = 32 +buttonspecs = { + create_party: { + y_off: start_y, + text: "Create Troupe" + }, + join_party: { + y_off: start_y + 64 + padding, + text: "Join Troupe" + } +} + + +mod.load = () -> + print("creating main menu") + main.root("screen")\append(mod.node) + for name, button_tbl in pairs buttonspecs + button_extra = ui.create_big_button(mod.node,6,start_x,button_tbl.y_off) + button_extra.node\append(am.translate(start_x+((nwidth*section_width)/2),button_tbl.y_off - 32)^ am.text(button_tbl.text,color.fg)) + mod[name] = button_extra + touch_indicator = am.group! + touch_cursor = am.sprite("data/cursor.png",vec4(1,1,1,1),"left","top") + touch_loc = am.translate(0,0)^ touch_cursor + touch_indicator\append(touch_loc) + mod.node\append(am.translate(0,-150)^ am.scale(3)^ am.text("Fools Rush In")) + mod.node\action("click_interpreter",coroutine.create(()-> + while true + print("Main menu loop") + if #main.win\active_touches! > 0 + touch = main.win\touch_position(1) + touch_cursor.color = vec4(1,1,1,1) + touch_loc.x = touch.x + touch_loc.y = touch.y + col_but = bp.check(touch.x, touch.y+64) + if #col_but > 0 + touch_cursor.color = vec4(0.5,0.5,0.5,0.5) + if #col_but > 0 + print("Collided with button:",col_but) + print("mod.create prty was:",mod.create_party) + if col_but[1] == mod.create_party + print("Creating party") + create_party = require "create_party_menu" + table.insert(main.action_queue,{create_party.load, {}}) + mod.unload! + coroutine.yield! + elseif col_but[1] == mod.join_party + print("Joining party") + join_party = require "join_party_menu" + table.insert(main.action_queue,{join_party.load, {}}) + mod.unload! + coroutine.yield! + else + touch_cursor.color = vec4(0,0,0,0) + coroutine.yield! + )) + mod.node\append(touch_indicator) + +mod.unload = () -> + main.root("screen")\remove(mod.node) + bp.remove(mod.create_party) + bp.remove(mod.join_party) + +mod diff --git a/src/party.moon b/src/party.moon new file mode 100644 index 0000000..1c2fb7b --- /dev/null +++ b/src/party.moon @@ -0,0 +1,58 @@ +-- Describes a player or enemy's party +-- Parties can exist without any members +room = require("room") +main = require("main") +char = require("char") +import Character from char +import LobbyRoom from room +mod = ... + +class Party + new: => + @members = {} + @rnode = am.group! + @node = am.translate(0,0) + @rnode\append(@node) + @room = LobbyRoom! + member: (uname) => + assert(uname, "cannot find a nil party member") + assert(type(uname) == "string", "Member should be called with a peer id") + @members[uname] + nmembers: () => + i = 0 + for k,v in pairs(@members) + i += 1 + i + add_member: (member) => + assert(member.uname, "cannot add a nil party member") + assert(type(member.uname) == "string", "Member should be called with a peer id") + @members[member.uname] = member + member.party = @ + @node\append(member.node) + remove_member: (member) => + @members[member.uname] = nil + member.party = nil + @node\remove(member.node) + set_room: (nroom) => + @room = nroom + for id, char in pairs(@members) + char\enter_room(nroom) + + serialize: () => + members = {} + for _, member in pairs(@members) + members[member.uname] = member\serialize! + am.to_json(members) + + deserialize: (data) -> + rp = Party! + tbl = am.parse_json(data) + for uname, payload in pairs(tbl) + tchar = Character.deserialize(payload) + rp\add_member(tchar) + rp + + + +mod["Party"] = Party +mod diff --git a/src/pixelize.moon b/src/pixelize.moon new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/pixelize.moon diff --git a/src/player.moon b/src/player.moon new file mode 100644 index 0000000..076f9bb --- /dev/null +++ b/src/player.moon @@ -0,0 +1,49 @@ +char = require "char" +util = require "util" +main = require "main" +bp = require "broadphase" +import KeyInput from char +import Vec2 from util + +mod = ... +--[[A player that we receive information about]] +class RemotePlayer extends char.Character + new: (uname, data, charclass) => + super(uname, data, charclass) + +class LocalPlayer extends char.Character + new:(uname, data, charclass) => + super(uname, data, charclass) + + draw: (...) => + super(...) + healthnode = am.group() ^ {am.translate(-main.width/2,main.height/2) ^ am.text(string.format("Health:%d", @health), vec4(255,255,255,255), "left","top")\tag "health text"} + @node\append(healthnode) + + take_damage: (_from, ammt) => + if am.current_time! - @last_dammage > 1 + @health -= ammt + @last_dammage = am.current_time! + (@node)("health text").text = string.format("Health:%d",@health) + if @health == 0 + @\die! + + filter: (other) => + if other.canfall + return "cross" + if other.hurtbox + if other.owner ~= @ + @take_damage(other,1) + return "cross" + else + return "slide" + die: (...) => + --[[When the player dies, wait 100 ms, load the level, and drop them on the spawnpoint]] + + super(...) + + + +mod["RemotePlayer"] = RemotePlayer +mod["LocalPlayer"] = LocalPlayer +mod diff --git a/src/room.moon b/src/room.moon new file mode 100644 index 0000000..e578f50 --- /dev/null +++ b/src/room.moon @@ -0,0 +1,152 @@ +-- Room, can contain one or more parties +-- When things like abilities happen, they always happen on the room. +main = require "main" +char = require "char" +import Character from char +mod = ... + +-- Each room has 8 position, 4 for players, 4 for enemies +-- Room types: +-- normal : p p p p e e e e +-- 4 3 2 1 1 2 3 4 +-- surrounded: e e p p p p e e +-- 2 1 1 2 2 1 1 2 +-- 3 3 3 3 +-- 4 4 4 4 +-- ambush : e p p e e p p e +-- 1 1 1 1 1 1 1 1 +-- 2 2 2 2 2 2 2 2 +-- 3 3 3 3 3 3 3 3 +-- 4 4 4 4 4 4 4 4 +-- skirmish: e p e p e p e p +-- 1 1 1 1 1 1 1 1 +-- 2 2 2 2 2 2 2 2 +-- 3 3 3 3 3 3 3 3 +-- 4 4 4 4 4 4 4 4 + +class Room + @children = {} + new: (data) => + @parties = {} + @actions_taken = {} + @special = {} + @node = am.group! + @data = data or {locations: {{},{},{},{},{},{},{},{}}} + for position, ptbl in pairs(@data.locations) + for pid, character in pairs(ptbl) + if character.uname + --player character + ptbl[pid] = main.world.player_party\member(character.uname) + else + ptbl[pid] = Character.deserialize(character) + + add_party: (party) => + table.insert(@parties,party) + + at_location: (n) -> + return @data.locations[n] + + generate: (...) -> + mod.SimpleRoom! + + @__inherited: (child) => + assert(child.distribute_party, "Rooms must be able to distribute parties") + @@.children[child.__name] = child + mod[child.__name] = child + + serialize: () => + ser_tbl = { + name:@@__name, + data:@data + } + --Rewrite locations to serialize characters + for position, ptbl in pairs(ser_tbl.data.locations) + for pid, character in pairs(ptbl) + if character.uname + ptbl[pid] = {uname:character.uname} + else + ptbl[pid] = character\serialize! + ret = am.to_json(ser_tbl) + ret + + deserialize: (data) -> + tbl = am.parse_json(data) + typ = mod.Room.children[tbl.name] + typ(tbl.data) + + __tostring: () => + return string.format("<%s>",@@__name) + +class LobbyRoom extends Room + new: (...) => + super(...) + @floor_y = -208 + @node\append(am.sprite("data/lobby_bg.png")) + load:() => + main.root("bg")\append(@node) + unload:() => + main.root("bg")\remove(@node) + + distribute_party: (...) => + {} + + enemy_location_of: (position) => + assert(position, "Position my not be nil") + ((main.width/9) * position) + + player_location_of: (position) => + error("lobbyroom player_position_of") + assert(position, "Position may not be nil") + -((main.width/9) * position) + +class CampRoom extends Room + new: (...) => + super(...) + @floor_y = -207 + @node\append(am.sprite("data/camp.png")) + @data.locations = {{},{},{},{},{},{},{},{}} + load:() => + main.root("bg")\append(@node) + + unload:() => + main.root("bg")\remove(@node) + + distribute_party: (playerparty) => + for _,player in pairs(playerparty) + table.insert(ret[player.position],player) + +class SimpleRoom extends Room + new: (...) => + super(...) + @floor_y = -207 + @node\append(am.sprite("data/room.png")) + load:() => + main.root("bg")\append(@node) + + unload:() => + main.root("bg")\remove(@node) + + distribute_party:(playerparty, enemyparty) => + for _,player in pairs(playerparty.members) + loc = @data.locations[5-player.data.position] + table.insert(loc,player) + player\set_location(loc) + for _,enemy in pairs(enemyparty.members) + loc = @data.locations[enemy.data.position+4] + table.insert(loc,enemy) + enemy\set_location(loc) + + player_location_of: (position) => + assert(position, "Position may not be nil") + -((main.width/9) * position) + + enemy_location_of: (position) => + assert(position, "Position my not be nil") + ((main.width/9) * position) + + + +mod.Room = Room +mod.SimpleRoom = SimpleRoom + +mod diff --git a/src/ui.moon b/src/ui.moon new file mode 100644 index 0000000..f46d8b2 --- /dev/null +++ b/src/ui.moon @@ -0,0 +1,221 @@ +--sprite packing sepcs +bp = require "broadphase" +main = require "main" +action = require "action" +color = require "color" +reg = require "ability_reg" +char = require "char" +import Enemy from char +mod = ... + +mod.big_frame_top_left = { + texture: "ui.png", + s1: 0 + t1: 0 + s2: 0 + t2: 0 + --hold on, maybe don't do this since we would have to mess with imagebuffers and textures +} + +mod.create_big_button = (node, segments, x, y) -> + --Constants + section_width = 32 + + ret = {} + trans = am.translate(x,y) + button = am.group! + button\append(trans^ am.sprite("data/big_frame_top_left.png",vec4(1,1,1,1),"left","top")) + for i = 1,segments - 2 + button\append(am.translate(((-32)*(segments/2)) + 32*i,y)^ am.sprite("data/big_frame_top_mid.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate((32*((segments/2)-1)),y)^ am.sprite("data/big_frame_top_right.png",vec4(1,1,1,1),"left","top")) + ret["node"] = button + bp.add(ret,x,y,segments*32,64) + node\append(button) + ret + + +mod.create_small_button = (node, segments, x, y) -> + assert(node ~= nil, "node was nil") + assert(type(segments) == "number","segments must be a numbeR") + assert(segments > 1, "segments must be at least 2") + --Constants + section_width = 18 + section_height = 40 + + ret = {} + trans = am.translate(x,y) + button = am.group! + button\append(trans^ am.sprite("data/small_frame_left.png",vec4(1,1,1,1),"left","top")) + for i = 1,segments - 2 + button\append(am.translate(x + (section_width*i),y)^ am.sprite("data/small_frame_mid.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x+(section_width*(segments - 1)),y)^ am.sprite("data/small_frame_right.png",vec4(1,1,1,1),"left","top")) + ret["node"] = button + bp.add(ret,x,y,segments*section_width,section_height) + node\append(button) + ret + +mod.create_any_button = (node, x_segs, y_segs, x, y) -> + assert(x_segs >= 2, "x must have at least 2 segments") + assert(y_segs >= 2, "y must have at leats 2 segments") + section_width, section_height = 32,32 + ret = {} + button = am.group! + button\append(am.translate(x,y)^ am.sprite("data/any_frame_top_left.png",vec4(1,1,1,1),"left","top")) + for i = 1, x_segs - 2 + button\append(am.translate(x + (i * section_width),y)^ am.sprite("data/any_frame_top_mid.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x + ((x_segs-1)*section_width),y)^ am.sprite("data/any_frame_top_right.png",vec4(1,1,1,1),"left","top")) + for i = 1, y_segs - 2 + button\append(am.translate(x,y - (i * section_height))^ am.sprite("data/any_frame_mid_left.png",vec4(1,1,1,1),"left","top")) + for j = 1, x_segs - 2 + button\append(am.translate(x + (j * section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_mid.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x + ((x_segs-1)*section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_right.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x,y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_left.png",vec4(1,1,1,1),"left","top")) + for i = 1, x_segs - 2 + button\append(am.translate(x + (i * section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_mid.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x+((x_segs-1)*section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_right.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x,y)\tag("loc")) + ret["node"] = button + bp.add(ret,x,y,x_segs*section_width,y_segs*section_height) + node\append(button) + ret + +mod.create_char_selector2 = (node) -> + left = mod.create_any_button(node,2,2,96,100) + right = mod.create_any_button(node,2,2,-96-(32*2),100) + text = am.text("Loading...",vec4(1,1,1,1),"center","top") + node\append(am.translate(0,100)^ text) + {left, right, text} + +mod.create_lobby_player = (node, peerid) -> + floor_y = -200 + rng_x = math.random(main.width) - (main.width/2) + +mod.create_char_selector = (node) -> + s = am.group! + slx = (-96/2)-18 + sly = (96/2) + srx = (96/2) + sry = (96/2) + scroll_left_node = am.translate(slx,sly)^ am.sprite("data/selector_left.png", vec4(1,1,1,1), "left","top") + scroll_left = { + node: scroll_left_node + } + scroll_right_node = am.translate(srx,sry)^ am.sprite("data/selector_right.png", vec4(1,1,1,1), "left","top") + scroll_right = { + node: scroll_right_node + } + s\append(scroll_left_node) + s\append(scroll_right_node) + bp.add(scroll_left,slx,sly,18,40) + bp.add(scroll_right,srx,sry,18,40) + node\append(s) + {scroll_left, scroll_right} + +mod.fadeout = () -> + fadeout_walltime = 0.1 + screen = main.root("screen") + hw = main.width/2 + hh = main.height/2 + bg = color.bg + start_color = vec4(bg.r,bg.g,bg.b,0) + fadeout = am.rect(-hw,hh,hw,-hh,color.bg)\tag("fade") + screen\action(am.tween(0.1, { + color: color.bg + })) + +mod.fadein = () -> + fadein_walltime = 0.1 + fade = main.root("fade") + fade\action(am.tween(0.1, { + color: color.transparent + })) + +mod.create_join_input = (node) -> + segments=3 + button_width = segments*18 + button_height = 40 + cell_padding = 18 + grid_w = 4 + grid_h = 4 + buttons = {} + --but = mod.create_small_button(node,3,-36,0) + start_x = cell_padding - ((grid_w/2) * button_width) - (((grid_w - 1)/2) * cell_padding) + start_y = 0 + ((grid_h/2) * button_height) + (((grid_h - 1)/2) * cell_padding) + for i = 0,grid_h - 1 + for j = 0, grid_w - 1 + button_x = start_x + (j * button_width) + ((j-1) * cell_padding) + button_y = start_y - (i * button_height) - ((i-1) * cell_padding) + small_button = mod.create_small_button(node,3,button_x,button_y) + table.insert(buttons, small_button) + rnode = small_button.node + text_map = { + "0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f" + } + bt = text_map[(i * grid_w) + j + 1] + rnode\append(am.translate(button_x+16,button_y-10)^ am.text(bt,vec4(1,1,1,1),"left","top")) + small_button["text"] = bt + + selected = mod.create_big_button(node,6,-32*3,-100) + buttons + +mod.tween_hit = (char, target, at_f) -> -- char is a character, target is a location (number) + x_from, x_to = char.node("char_translate").x, 0 + room = char.room + if char.__class == Enemy then + --x_from = room\enemy_location_of(char.data.location) + x_to = room\player_location_of(target) + else + --x_from = room\player_location_of(char.data.location) + x_to = room\enemy_location_of(target) + am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_to},am.windup)) + if at_f + at_f! + am.wait(am.tween(char.node("char_translate"), 0.1, {x: x_from},am.linear)) + +mod.build_infocard = (node, ability) -> + x_segs = 10 + y_segs = 4 + x = -310 + y = 80 + section_width, section_height = 32,32 + button = am.group! + button\append(am.translate(x,y)^ am.sprite("data/any_frame_top_left.png",vec4(1,1,1,1),"left","top")) + for i = 1, x_segs - 2 + button\append(am.translate(x + (i * section_width),y)^ am.sprite("data/any_frame_top_mid.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x + ((x_segs-1)*section_width),y)^ am.sprite("data/any_frame_top_right.png",vec4(1,1,1,1),"left","top")) + for i = 1, y_segs - 2 + button\append(am.translate(x,y - (i * section_height))^ am.sprite("data/any_frame_mid_left.png",vec4(1,1,1,1),"left","top")) + for j = 1, x_segs - 2 + button\append(am.translate(x + (j * section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_mid.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x + ((x_segs-1)*section_width),y - (i * section_height))^ am.sprite("data/any_frame_mid_right.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x,y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_left.png",vec4(1,1,1,1),"left","top")) + for i = 1, x_segs - 2 + button\append(am.translate(x + (i * section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_mid.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x+((x_segs-1)*section_width),y - ((y_segs-1)*section_height))^ am.sprite("data/any_frame_bot_right.png",vec4(1,1,1,1),"left","top")) + button\append(am.translate(x,y)\tag("loc")) + button("loc")\append(am.scale(2)^ am.translate(16,-8)^ am.text(ability.text, color.fg, "left","top")) + button("loc")\append(am.scale(1)^ am.translate(16,-48)^ am.text(ability.description, color.fg, "left","top")) + button("loc")\append(am.scale(1)^ am.translate(16,-80)^ am.text("Speed:" .. tostring(ability.speed), color.fg, "left","top")) + button("loc")\append(am.scale(1)^ am.translate(16,-108)^ am.text("Hits:",color.fg,"left","center")) + for i = 1,8 + button("loc")\append(am.translate(42 + (i*24), -96)^ am.sprite("data/pip_frame.png",color.white,"left","top")) + if ability.hits_icon[i] == 1 + button("loc")\append(am.translate(42 + (i*24), -96)^ am.sprite("data/pip_light.png",color.white,"left","top")) + + node\append(button) + button + + +mod.battle_log = (text) -> + sx, sy = math.random(-100,100), math.random(-40,40) + trans = am.translate(sx,sy) + trans\action(am.tween(3,{y:sy + 40})) + t = am.text(text, color.fg) + t\action(am.tween(3,{color:color.transparent})) + t\action(coroutine.create( () -> + am.wait(am.delay(4)) + main.root("screen")\remove(trans) + )) + main.root("screen")\append(trans^ t) + +mod diff --git a/src/util.moon b/src/util.moon new file mode 100644 index 0000000..3066d86 --- /dev/null +++ b/src/util.moon @@ -0,0 +1,37 @@ +--[[Utility classes]] + +mod = ... + +class Vec3 + new:(x,y,z) => + @x = x or 0 + @y = y or 0 + @z = z or 0 + +class Vec2 + new: (x,y) => + @x = x or 0 + @y = y or 0 + area: () => math.abs(@x * @y) + __sub: (a,b) -> + Vec2(a.x-b.x,a.y-b.y) + __add: (a,b) -> + Vec2(a.x + b.x, a.y + b.y) + +--getmetatable(Vec2).__sub = (a,b) -> + --Vec2(a.x-b.x,b.x-b.y) + +drawable = (c) -> + assert(c.sprite or c.anim) + +anim_co = (a) -> + while true + a.keyframe = math.floor(am.current_time()*4) % #(a.anim) + assert(a.anim[a.keyframe + 1], "Failed to find an appropriate image to draw.") + a.node\replace("sprite",am.sprite(a.anim[a.keyframe + 1])\tag "sprite") + coroutine.yield() + + +mod["Vec2"] = Vec2 +mod["Vec3"] = Vec3 +mod diff --git a/src/world.moon b/src/world.moon new file mode 100644 index 0000000..c7b106c --- /dev/null +++ b/src/world.moon @@ -0,0 +1,368 @@ +-- Handles singleton logic + +mod = ... +connect = require "connect" +lobby = require "lobby" +joined = require "joined" +party = require "party" +char = require "char" +import Character from char +import Enemy from char +import Party from party +player = require "player" +import RemotePlayer from player +import LocalPlayer from player +main = require "main" +room = require "room" +import Room from room +import LobbyRoom from room +ui = require "ui" +ability = require "ability_reg" + +class Server + new:() => + @server = true + @client = false + am.eval_js(connect) + am.eval_js(lobby) + @lobby_id = am.eval_js("GLOBAL.lobby_id") + @game_state = "lobby" --lobby, room_entry, room_players, room_battle_animate, victory, camp_entry, camp_players_animate, defeat, done + @game_state_extra = 0 + @players = {} --[peer_id] = tbl + @set_actions = {} --[peer_id] = "name" + @player_party = nil -- the party, created at campaign start + @host = nil --who is the lobby host, with the power to start the game? + @enemy_party = nil -- The enemy party + @updates = {} + @dead_players = {} --[peer_id] = true + @cr = 1 + player_joined:(msg)=> + peer = msg.peer + @players[peer] = RemotePlayer(peer,nil,char.classes.Tumbler) + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_player_joined",uname:peer,class:"Tumbler"}))) + if @host == nil + @host = peer + request_class_change:(msg)=> + if @game_state == "lobby" + if @players[msg.peer] + @players[msg.peer]\set_class(char.classes[msg.class]) + else + @players[msg.peer] = RemotePlayer(msg.peer,nil,char.classes[msg.class]) + am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "confirm_class_change", class: msg.class}))) + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_class_change",uname:msg.peer,class:msg.class}))) + else + am.eval_js(string.format("GLOBAL.send_message(%q,%q)",msg.peer,am.to_json({msg: "deny_class_change", class:@players[msg.peer].class}))) + request_campaign_start:(msg)=> + if msg.peer == @host and @game_state == "lobby" + @player_party = Party! + --Set everyone's position, hp, ect. + for _, chartbl in pairs(@players) + chartbl.position = chartbl.class.default_position + chartbl.hp = chartbl.class.default_hp + @player_party\add_member(chartbl) + @campaign_start! + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_campaign_start",time_ref:@player_start_time}))) + campaign_start: => + @game_state = "room_entry" + @player_start_time = am.eval_js("new Date().getTime()") + room = Room.generate(@cr) + @room = room + @enemy_party = @generate_enemies! + @enemy_party\set_room(room) + @player_party\set_room(room) + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ + msg: "info_enemy_party", + data: @enemy_party\serialize! + }))) + room\distribute_party(@player_party,@enemy_party) + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"info_room",data:room\serialize!,time_ref:@player_start_time}))) + @game_state = "room_players" + + request_player_list:(msg)=> + player_ser = {} + for peerid, player in pairs(@players) + player_ser[peerid] = player\serialize! + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({msg:"respond_player_list", data:player_ser}))) + set_action: (msg)=> + if @game_state == "room_players" + if not @dead_players[msg.peer] + @set_actions[msg.peer] = ability[msg.action] + update:() => + msg = am.eval_js("GLOBAL.get_message()") + if msg != nil + if msg.msg == "data" + info = am.parse_json(msg.data) + info.peer = msg.peer -- server messages have an extra "peer" field that the client didn't add. + if @[info.msg] + @[info.msg](@,info) + else + print("Failed to find server message handler:",msg,"no handler",info.msg) + else + print("Msg was nil") + --actual game loop + if @game_state == "room_players" + if am.eval_js("new Date().getTime()") > @player_start_time + 6000 + --Generate some random actions from npcs + npc_actions = {} + used_actions = {} + party_index = {} + character_index = {} + for uname, npc in pairs(@enemy_party.members) + npc_actions[uname] = npc\select_action! + table.insert(used_actions, npc_actions[uname]) + party_index[npc_actions[uname]] = @enemy_party + character_index[npc_actions[uname]] = npc + total_actions = {} + for k,v in pairs(npc_actions) + total_actions[k] = v.__name + for k,v in pairs(@set_actions) + total_actions[k] = v.__name + table.insert(used_actions, v) + party_index[v] = @player_party + character_index[v] = @player_party\member(k) + --Lock in actions, and send it back out (500 ms) + table.sort(used_actions, (a,b) -> + a.speed < b.speed + ) + -- actually do the actions + for k,v in ipairs(used_actions) + tchar = character_index[v] + v.__class.use(@,party_index[v],tchar) + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ + msg:"info_actions", + data: total_actions + }))) + --sort by speed and apply + @game_state = "room_battle_animate" + @set_actions = {} + if @game_state == "room_battle_animate" --only exists for 1 tick, calc dammge, check if room is done, check if we're defeated, ect. + @calculate_damage + @player_start_time = am.eval_js("new Date().getTime()")+500 --500 ms for animations + @game_state = "room_players" + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ + msg:"info_timeref", + time_ref:@player_start_time + }))) + dead_characters = {} + for uname, char in pairs(@player_party.members) + if char.data.hp <= 0 + table.insert(dead_characters,uname) + char\die! + @player_party\remove_member(char) + @dead_players[uname] = true + for uname, char in pairs(@enemy_party.members) + if char.data.hp <= 0 + table.insert(dead_characters,uname) + char\die! + @enemy_party\remove_member(char) + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ + msg:"info_deaths", + data:dead_characters + }))) + if @player_party\nmembers! == 0 + @game_state = "defeat" + elseif @enemy_party\nmembers! == 0 + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ + msg:"info_loot" + time_ref:@player_start_time + --Don't actually do loot, just go to the next room + }))) + @player_start_time = am.eval_js("new Date().getTime()") + @game_state = "victory" + if @game_state == "defeat" + am.eval_js(string.format("GLOBAL.broadcast(%q)",am.to_json({ + msg:"info_defeat" + }))) + @game_state = "done" + if @game_state == "victory" + if am.eval_js("new Date().getTime()") > @player_start_time + 6000 -- a few seconds for victory! (and animations) + @cr += 1 + @campaign_start! + + calculate_dammage:() => + print("calculating dammage...") + + generate_enemies:() => + enemies = {} + tcr = @cr + possible_enemies = {} + for k,v in pairs(char.enemies) + table.insert(possible_enemies,v) + enemy_party = Party! + while tcr > 0 and #possible_enemies > 0 + filtered_enemies = {} + for _, enemy in pairs(possible_enemies) + if enemy.cr <= tcr + table.insert(filtered_enemies,enemy) + table.shuffle(filtered_enemies) + rng_enemy = table.remove(filtered_enemies) + tcr = tcr - rng_enemy.cr + enemy_party\add_member(Enemy(nil,rng_enemy)) + enemy_party + +class World + new:() => + @client = true + @server = false + am.eval_js(connect) + @players = {} + @player_party = Party! + @enemy_party = nil + main.root("world_characters")\append(@player_party.node) + @parties = {@player_party} + @node = am.group! + @node\append(@player_party.node) + @node\action(coroutine.create(()-> + while true + coroutine.yield! + )) + @localplayer = nil + join:(id) => + am.eval_js(joined) + am.eval_js("CLIENT.join('" .. id .. "');") + client_open:() => + am.eval_js("CLIENT.open") + client_id:() => + am.eval_js("CLIENT.peer") + confirm_class_change: (msg) => + @localplayer\set_class(char.classes[msg.class]) + deny_class_change: (msg) => + @localplayer\set_class(char.classes[msg.class]) + respond_player_list: (msg) => + for peerid, chardata in pairs(msg.data) + if not @player_party\member(peerid) + if peerid == am.eval_js("CLIENT.peer._id") + @localplayer = Character.deserialize(chardata) + @player_party\add_member(@localplayer) + @localplayer\enter_room(@player_party.room) + else + newplayer = Character.deserialize(chardata) + @player_party\add_member(newplayer) + newplayer\enter_room(@player_party.room) + else + print("Do nothing...") + info_class_change: (msg) => + if msg.uname == @localplayer.uname + return + if not @player_party\member(msg.uname) + @player_party\add_member(Character.deserialize(msg.class)) + else + @player_party\member(msg.uname)\set_class(char.classes[msg.class]) + info_player_joined: (msg) => + if msg.uname == am.eval_js("CLIENT.peer._id") + if @localplayer != nil + return + @localplayer = LocalPlayer(msg.uname, {}, char.classes[msg.class]) + @player_party\add_member(@localplayer) + @localplayer\enter_room(@player_party.room) + elseif not @player_party\member(msg.uname) + newplayer = RemotePlayer(msg.uname, nil, char.classes[msg.class]) + @player_party\add_member(newplayer) + newplayer\enter_room(@player_party.room) + else + print("Do nothing") + info_campaign_start: (msg) => + lobby_menu = require "lobby_menu" + create_party_menu = require "create_party_menu" + if create_party_menu.loaded + create_party_menu.unload! + if lobby_menu.loaded + lobby_menu.unload! + battle_menu = require "battle_menu" + battle_menu.load! + ui.fadeout! + @time_ref = msg.time_ref + info_room: (msg) => + @room = Room.deserialize(msg.data) + @time_ref = msg.time_ref + @set_room(@room) + battle_menu = require "battle_menu" + battle_menu.victory = false + main.root\remove("infocard") + info_timeref: (msg) => + main.root\remove("infocard") + @time_ref = msg.time_ref + info_enemy_party: (msg) => + if @enemy_party + @node\remove(@enemy_party.rnode) + @enemy_party = Party.deserialize(msg.data) + @enemy_party\set_room(@room) + if @room.__class != LobbyRoom + @enemy_party\set_room(@room) + battle_menu = require "battle_menu" + battle_menu.victory = false + main.root\remove("infocard") + --@node\append(@enemy_party.node) + info_defeat: (msg) => + battle_menu = require("battle_menu") + defeat_menu = require("defeat_menu") + table.insert(main.action_queue,{battle_menu.unload, {}}) + table.insert(main.action_queue,{defeat_menu.load,{}}) + info_loot: (msg) => + @time_ref = msg.time_ref + battle_menu = require "battle_menu" + battle_menu.victory = true + main.root\remove("infocard") + info_updates: (msg) => + for uname, updated in pairs(msg.data) + tchar = @player_party\member(uname) or @enemy_party\member(uname) + for k,v in pairs(updated) + tchar.data[k] = v + info_deaths: (msg) => + for _, uname in pairs(msg.data) + if @player_party\member(uname) + tchar = @player_party\member(uname) + tchar\die! + @player_party\remove_member(tchar) + elseif @enemy_party\member(uname) + tchar = @enemy_party\member(uname) + tchar\die! + @enemy_party\remove_member(tchar) + info_actions: (msg) => + for uname, action_name in pairs(msg.data) + action = ability[action_name] + if @player_party\member(uname) + action.use(@,@player_party,@player_party\member(uname)) + ui.battle_log(string.format("%s used %s",@player_party\member(uname).class.name,action.text)) + elseif @enemy_party\member(uname) + action.use(@,@enemy_party,@enemy_party\member(uname)) + ui.battle_log(string.format("%s used %s",uname,action.text)) + main.root\remove("infocard") + update: () => + msg = am.eval_js("CLIENT.get()") + if msg != nil + info = am.parse_json(msg) + if @[info.msg] + @[info.msg](@,info) + else + print("Failed to find client message handler", info) + set_local: (player) => + @localplayer = player + @player_party\add_member(player) + @localplayer\enter_room(@player_party.room) + set_room: (room) => + @room = room + assert(@room, "cannot set a nil room") + assert(@room.load, "rooms must have a .load") + if @player_party.room + @player_party.room\unload! + --@node\remove(@player_party.room.node) + @room\load! + --@node\append(room.node) + @player_party\set_room(room) + if @enemy_party + if @enemy_party.room + @enemy_party.room\unload! + @enemy_party\set_room(@room) + --new_rat = Enemy(nil,char.enemies.Rat) + if @enemy_party + --@enemy_party\add_member(new_rat) + --@enemy_party\set_room(room) + @node\append(@enemy_party.rnode) + load: () => + main.root("world_characters")\append(@node) + + +mod["World"] = World +mod["Server"] = Server +mod |
