From c2926c5ec74d7e37da395c8c32a7ff2b4cae7d06 Mon Sep 17 00:00:00 2001 From: Alexander M Pickering Date: Mon, 29 Jan 2024 16:20:10 -0600 Subject: All the files --- src/a_brood.moon | 42 +++ src/a_dance.moon | 49 +++ src/a_drum.moon | 48 +++ src/a_firebreath.moon | 51 +++ src/a_hackysacks.moon | 50 +++ src/a_highjump.moon | 49 +++ src/a_knifeslip.moon | 51 +++ src/a_mope.moon | 41 +++ src/a_pass.moon | 34 ++ src/a_physique.moon | 48 +++ src/a_rat_bite.moon | 42 +++ src/a_rat_scurry.moon | 25 ++ src/a_ruminate.moon | 29 ++ src/a_strum.moon | 37 +++ src/a_sulk.moon | 43 +++ src/a_test.moon | 52 +++ src/a_tumble.moon | 51 +++ src/ability_reg.moon | 43 +++ src/action.moon | 58 ++++ src/battle_menu.moon | 129 ++++++++ src/broadphase.moon | 69 ++++ src/bump.lua | 773 ++++++++++++++++++++++++++++++++++++++++++++ src/char.moon | 307 ++++++++++++++++++ src/char_fool.moon | 24 ++ src/char_jugg.moon | 30 ++ src/char_mage.moon | 26 ++ src/char_tank.moon | 34 ++ src/char_theif.moon | 25 ++ src/color.moon | 13 + src/constants.lua | 7 + src/constrain.lua | 109 +++++++ src/create_party_menu.moon | 103 ++++++ src/defeat_menu.moon | 66 ++++ src/dispatch.moon | 49 +++ src/e_bethany.moon | 28 ++ src/e_child.moon | 28 ++ src/e_mopey_marvin.moon | 28 ++ src/e_rat.moon | 31 ++ src/e_ruminating_randy.moon | 28 ++ src/e_sullen_salley.moon | 29 ++ src/ext.lua | 66 ++++ src/join_party_menu.moon | 62 ++++ src/js/connect.js | 8 + src/js/joined.js | 48 +++ src/js/lobby.js | 80 +++++ src/lobby_menu.moon | 80 +++++ src/main.moon | 72 +++++ src/main_menu.moon | 81 +++++ src/party.moon | 58 ++++ src/pixelize.moon | 0 src/player.moon | 49 +++ src/room.moon | 152 +++++++++ src/ui.moon | 221 +++++++++++++ src/util.moon | 37 +++ src/world.moon | 368 +++++++++++++++++++++ 55 files changed, 4161 insertions(+) create mode 100644 src/a_brood.moon create mode 100644 src/a_dance.moon create mode 100644 src/a_drum.moon create mode 100644 src/a_firebreath.moon create mode 100644 src/a_hackysacks.moon create mode 100644 src/a_highjump.moon create mode 100644 src/a_knifeslip.moon create mode 100644 src/a_mope.moon create mode 100644 src/a_pass.moon create mode 100644 src/a_physique.moon create mode 100644 src/a_rat_bite.moon create mode 100644 src/a_rat_scurry.moon create mode 100644 src/a_ruminate.moon create mode 100644 src/a_strum.moon create mode 100644 src/a_sulk.moon create mode 100644 src/a_test.moon create mode 100644 src/a_tumble.moon create mode 100644 src/ability_reg.moon create mode 100644 src/action.moon create mode 100644 src/battle_menu.moon create mode 100644 src/broadphase.moon create mode 100644 src/bump.lua create mode 100644 src/char.moon create mode 100644 src/char_fool.moon create mode 100644 src/char_jugg.moon create mode 100644 src/char_mage.moon create mode 100644 src/char_tank.moon create mode 100644 src/char_theif.moon create mode 100644 src/color.moon create mode 100644 src/constants.lua create mode 100644 src/constrain.lua create mode 100644 src/create_party_menu.moon create mode 100644 src/defeat_menu.moon create mode 100644 src/dispatch.moon create mode 100644 src/e_bethany.moon create mode 100644 src/e_child.moon create mode 100644 src/e_mopey_marvin.moon create mode 100644 src/e_rat.moon create mode 100644 src/e_ruminating_randy.moon create mode 100644 src/e_sullen_salley.moon create mode 100644 src/ext.lua create mode 100644 src/join_party_menu.moon create mode 100644 src/js/connect.js create mode 100644 src/js/joined.js create mode 100644 src/js/lobby.js create mode 100644 src/lobby_menu.moon create mode 100644 src/main.moon create mode 100644 src/main_menu.moon create mode 100644 src/party.moon create mode 100644 src/pixelize.moon create mode 100644 src/player.moon create mode 100644 src/room.moon create mode 100644 src/ui.moon create mode 100644 src/util.moon create mode 100644 src/world.moon (limited to 'src') 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 = "") => + @time = 0 + @key = key + @value = false + +--print "test" + +class AnimFrame + new:(anim,interupt,mode) => + @anim = anim + @interupt = interupt + @mode = mode + +class ActionInput + new:(action = "") => + @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(;o0){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>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(rn(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(nr(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;nt.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;e0?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{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{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=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.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=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","afrokick ","ericz ","Jairo ","Jonas Gloning <34194370+jonasgloning@users.noreply.github.com>","Jairo Caro-Accino Viciana ","Carlos Caballero ","hc ","Muhammad Asif ","PrashoonB ","Harsh Bardhan Mishra <47351025+HarshCasper@users.noreply.github.com>","akotynski ","lmb ","Jairooo ","Moritz Stückler ","Simon ","Denis Lukov ","Philipp Hancke ","Hans Oksendahl ","Jess ","khankuan ","DUODVK ","XiZhao ","Matthias Lohr ","=frank tree <=frnktrb@googlemail.com>","Andre Eckardt ","Chris Cowan ","Alex Chuev ","alxnull ","Yemel Jardi ","Ben Parnell ","Benny Lichtner ","fresheneesz ","bob.barstead@exaptive.com ","chandika ","emersion ","Christopher Van ","eddieherm ","Eduardo Pinho ","Evandro Zanatta ","Gardner Bickford ","Gian Luca ","PatrickJS ","jonnyf ","Hizkia Felix ","Hristo Oskov ","Isaac Madwed ","Ilya Konanykhin ","jasonbarry ","Jonathan Burke ","Josh Hamit ","Jordan Austin ","Joel Wetzell ","xizhao ","Alberto Torres ","Jonathan Mayol ","Jefferson Felix ","Rolf Erik Lekang ","Kevin Mai-Husan Chia ","Pepijn de Vos ","JooYoung ","Tobias Speicher ","Steve Blaurock ","Kyrylo Shegeda ","Diwank Singh Tomer ","Sören Balko ","Arpit Solanki ","Yuki Ito ","Artur Zayats "],"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 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 -- cgit v1.2.3-70-g09d2