-- 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