diff --git a/datasrc/client.dts b/datasrc/client.dts new file mode 100644 index 000000000..12189231f --- /dev/null +++ b/datasrc/client.dts @@ -0,0 +1,80 @@ +struct image { + int id = 0 + string filename = filename@1 +} + +struct spriteset { + ptr:image img = @1 + int gridx = @2 + int gridy = @3 +} + +struct sprite { + ptr:spriteset set = parent + int x = @1 + int y = @2 + int w = @3 + int h = @4 +} + +struct sound { + int id = 0 + string filename = @0 +} + +struct soundset { + int last = 0 + array:sound sounds = * +} + +struct particleinfo { + ptr:sprite spr = sprite@1 + float color_r = color@1 + float color_g = color@2 + float color_b = color@3 + float color_a = color@4 + int lifemod = life@1 +} + +struct weapon { + ptr:sprite sprite_body = sprite_body@1 + ptr:sprite sprite_cursor = sprite_cursor@1 + ptr:sprite sprite_proj = sprite_proj@1 + int recoil = recoil@1 + int visual_size = visual_size@1 +} + +struct keyframe { + float time = @0 + float x = @1 + float y = @2 + float angle = @3 +} + +struct sequence { + array:keyframe frames = * +} + +struct animation { + instance:sequence body = body + instance:sequence back_foot = back_foot + instance:sequence front_foot = front_foot + instance:sequence attach = attach +} + +struct data_container { + array:image images = images.* + array:spriteset spritesets = sprites.* + array:sprite sprites = sprites.*.* + array:weapon weapons = weapons.* + array:particleinfo particles = particles.* + + array:soundset sounds = sounds.* + array:animation animations = animations.* +} + +const array:int weapon = weapons.* +const array:int sound = sounds.* +const array:int image = images.* +const array:int sprite = sprites.*.* +const array:int anim = animations.* diff --git a/datasrc/server.dts b/datasrc/server.dts new file mode 100644 index 000000000..41277b60f --- /dev/null +++ b/datasrc/server.dts @@ -0,0 +1,5 @@ +struct data_container { +} + +const array:int sound = sounds.* +const array:int weapon = weapons.* diff --git a/datasrc/teewars.ds b/datasrc/teewars.ds new file mode 100644 index 000000000..7e083d629 --- /dev/null +++ b/datasrc/teewars.ds @@ -0,0 +1,415 @@ +sounds { + gun_fire { + "data/audio/wp_gun_fire-01.wav" + "data/audio/wp_gun_fire-02.wav" + "data/audio/wp_gun_fire-03.wav" + } + + shotgun_fire { + "data/audio/wp_shotty_fire-01.wav" + "data/audio/wp_shotty_fire-02.wav" + "data/audio/wp_shotty_fire-03.wav" + } + + rocket_fire { + "data/audio/wp_flump_launch-01.wav" + "data/audio/wp_flump_launch-02.wav" + "data/audio/wp_flump_launch-03.wav" + } + + hammer_fire { + "data/audio/wp_hammer_swing-01.wav" + "data/audio/wp_hammer_swing-02.wav" + "data/audio/wp_hammer_swing-03.wav" + } + + ninja_fire { + "data/audio/wp_ninja_attack-01.wav" + "data/audio/wp_ninja_attack-02.wav" + "data/audio/wp_ninja_attack-03.wav" + } + + rocket_explode { + "data/audio/wp_flump_explo-01.wav" + "data/audio/wp_flump_explo-02.wav" + "data/audio/wp_flump_explo-03.wav" + } + + ninja_hit { + "data/audio/wp_ninja_hit-01.wav" + "data/audio/wp_ninja_hit-02.wav" + "data/audio/wp_ninja_hit-03.wav" + } + + weapon_switch { + "data/audio/wp_switch-01.wav" + "data/audio/wp_switch-02.wav" + "data/audio/wp_switch-03.wav" + } + + player_pain_short { + "data/audio/vo_teefault_pain_short-01.wav" + "data/audio/vo_teefault_pain_short-02.wav" + "data/audio/vo_teefault_pain_short-03.wav" + "data/audio/vo_teefault_pain_short-04.wav" + "data/audio/vo_teefault_pain_short-05.wav" + "data/audio/vo_teefault_pain_short-06.wav" + "data/audio/vo_teefault_pain_short-07.wav" + "data/audio/vo_teefault_pain_short-08.wav" + "data/audio/vo_teefault_pain_short-09.wav" + "data/audio/vo_teefault_pain_short-10.wav" + "data/audio/vo_teefault_pain_short-11.wav" + "data/audio/vo_teefault_pain_short-12.wav" + } + + player_pain_long { + "data/audio/vo_teefault_pain_long-01.wav" + "data/audio/vo_teefault_pain_long-02.wav" + } + + body_land { + "data/audio/foley_land-01.wav" + "data/audio/foley_land-02.wav" + "data/audio/foley_land-03.wav" + "data/audio/foley_land-04.wav" + } + + player_jump { + "data/audio/foley_foot_left-01.wav" + "data/audio/foley_foot_left-02.wav" + "data/audio/foley_foot_left-03.wav" + "data/audio/foley_foot_left-04.wav" + "data/audio/foley_foot_right-01.wav" + "data/audio/foley_foot_right-02.wav" + "data/audio/foley_foot_right-03.wav" + "data/audio/foley_foot_right-04.wav" + } + + player_die { + "data/audio/foley_body_splat-02.wav" + "data/audio/foley_body_splat-03.wav" + "data/audio/foley_body_splat-04.wav" + } + + player_spawn { + "data/audio/vo_teefault_spawn-01.wav" + "data/audio/vo_teefault_spawn-02.wav" + "data/audio/vo_teefault_spawn-03.wav" + "data/audio/vo_teefault_spawn-04.wav" + "data/audio/vo_teefault_spawn-05.wav" + "data/audio/vo_teefault_spawn-06.wav" + "data/audio/vo_teefault_spawn-07.wav" + } + + tee_cry { + "data/audio/vo_teefault_cry-01.wav" + "data/audio/vo_teefault_cry-02.wav" + } + + hook_loop { + "data/audio/hook_loop-01.wav" + "data/audio/hook_loop-02.wav" + } + + hook_attach { + "data/audio/hook_attach-01.wav" + "data/audio/hook_attach-02.wav" + "data/audio/hook_attach-03.wav" + } +} + + +images { + weapons { + filename "data/tileset_weapons.png" + } + + game { + filename "data/game_main.png" + } + + particles { + filename "data/tileset_particles.png" + } + + sun { + filename "data/sun.png" + } + + char_default { + filename "data/char_teefault.png" + } +} + +particles { + part1 { + sprite sprites.particles.part1 + color 0.7 0.7 0.7 1.0 + life 50 + } + + part2 { + sprite sprites.particles.part2 + color 1.0 1.0 1.0 1.0 + life 50 + } + + part3 { + sprite sprites.particles.part3 + color 0.8 0.8 0.8 1.0 + life 50 + } + + part4 { + sprite sprites.particles.part4 + color 0.98 0.1 0.16 1.0 + life 70 + } + + part5 { + sprite sprites.particles.part5 + color 1.0 1.0 1.0 1.0 + life 70 + } + + part6 { + sprite sprites.particles.part6 + color 0.6 0.6 0.6 1.0 + life 100 + } + + part7 { + sprite sprites.particles.part7 + color 1.0 1.0 1.0 1.0 + life 100 + } + + part8 { + sprite sprites.particles.part8 + color 0.7 0.7 0.7 1.0 + life 150 + } + + part9 { + sprite sprites.particles.part9 + color 1.0 1.0 1.0 1.0 + life 40 + } +} + + +weapons { + gun { + sprite_body sprites.weapons.weapon_gun_body + sprite_cursor sprites.weapons.weapon_gun_cursor + sprite_proj sprites.weapons.weapon_gun_proj + + recoil 10 + reloadtime 100 + visual_size 64 + } + + rocket { + sprite_body sprites.weapons.weapon_rocket_body + sprite_cursor sprites.weapons.weapon_rocket_cursor + sprite_proj sprites.weapons.weapon_rocket_proj + + recoil 10 + reloadtime 600 + visual_size 96 + } + + shotgun { + sprite_body sprites.weapons.weapon_shotgun_body + sprite_cursor sprites.weapons.weapon_shotgun_cursor + sprite_proj sprites.weapons.weapon_shotgun_proj + + recoil 10 + reloadtime 800 + visual_size 96 + } + + hammer { + sprite_body sprites.weapons.weapon_hammer_body + sprite_cursor sprites.weapons.weapon_hammer_cursor + sprite_proj sprites.weapons.weapon_hammer_proj + + recoil 10 + reloadtime 100 + visual_size 96 + } +} + +sprites { + + particles images.particles 16 16 { + part1 2 0 2 2 + part2 4 0 2 2 + part3 6 0 2 2 + part4 8 0 2 2 + part5 10 0 2 2 + part6 2 2 2 2 + part7 4 2 2 2 + part8 6 2 2 2 + part9 8 2 2 2 + + star1 0 0 2 2 + star2 0 2 2 2 + } + + hud images.game 32 16 { + health_full 0 0 4 4 + health_empty 5 0 4 4 + armor_full 0 5 4 4 + armor_empty 5 5 4 4 + } + + weapons images.weapons 32 32 { + weapon_gun_body 2 4 4 2 + weapon_gun_cursor 0 4 2 2 + weapon_gun_proj 6 4 2 2 + weapon_gun_muzzle1 8 4 3 2 + weapon_gun_muzzle2 12 4 3 2 + weapon_gun_muzzle3 16 4 3 2 + + weapon_shotgun_body 2 6 8 2 + weapon_shotgun_cursor 0 6 2 2 + weapon_shotgun_proj 10 6 2 2 + weapon_shotgun_muzzle1 12 6 3 2 + weapon_shotgun_muzzle2 16 6 3 2 + weapon_shotgun_muzzle3 20 6 3 2 + + weapon_rocket_body 2 8 7 2 + weapon_rocket_cursor 0 8 2 2 + weapon_rocket_proj 10 8 2 2 + + weapon_hammer_body 2 1 4 3 + weapon_hammer_cursor 0 0 2 2 + weapon_hammer_proj 0 0 0 0 + + weapon_ninja_body 2 10 7 2 + weapon_ninja_cursor 0 10 2 2 + weapon_ninja_proj 0 0 0 0 + + hook_chain 2 0 1 1 + hook_head 3 0 2 1 + + hadoken1 1 12 7 4 + hadoken2 8 12 8 4 + hadoken3 17 12 7 4 + } + + powerups images.weapons 32 32 { + powerup_health 10 2 2 2 + powerup_armor 12 2 2 2 + powerup_weapon 3 0 6 2 + powerup_ninja 3 10 7 2 + powerup_timefield 3 0 6 2 + } + + tees images.char_default 16 64 { + tee_body 0 0 4 4 + tee_body_outline 4 0 4 4 + tee_foot 8 3 2 1 + tee_foot_outline 8 2 2 1 + + tee_eye_normal 10 2 1 1 + } +} + +animations { + base { + body { + 0.0 0 -4 0 + } + + back_foot { + 0.0 0 10 0 + } + + front_foot { + 0.0 0 10 0 + } + + attach { + } + } + + idle { + body { + } + + back_foot { + 0.0 -7 0 0 + } + + front_foot { + 0.0 7 0 0 + } + + attach { + 0.0 0 0 0 + } + } + + inair { + body { + } + + back_foot { + 0.0 -3 0 -0.1 + } + + front_foot { + 0.0 3 0 -0.1 + } + + attach { + } + } + + walk { + body { + } + + front_foot { + 0.0 8 0 0 + 0.2 -8 0 0 + 0.4 -10 -4 0.2 + 0.6 -8 -8 0.3 + 0.8 4 -4 -0.2 + 1.0 8 0 0 + } + + back_foot { + 0.0 -10 -4 0.2 + 0.2 -8 -8 0.3 + 0.4 -4 -4 -0.2 + 0.6 0 0 0 + 0.8 -8 0 0 + 1.0 -10 -4 0.2 + } + + attach { + } + } + + hammer_swing { + body { + } + + front_foot { + } + + back_foot { + } + + attach { + 0.0 0 0 -0.10 + 0.3 0 0 0.25 + 0.4 0 0 0.30 + 0.5 0 0 0.25 + 1.0 0 0 -0.10 + } + } +} diff --git a/datasrc/teewars.dsd b/datasrc/teewars.dsd new file mode 100644 index 000000000..a95b960cb --- /dev/null +++ b/datasrc/teewars.dsd @@ -0,0 +1,24 @@ +tag:images { + ident:name * { + tag:filename string:filename + } +} + +tag:sounds { + ident:name * { + tag:filename string:path + } +} + +tag:weapons { + ident:name * { + tag:sprite_gun ptr:sprite + tag:sprite_cursor ptr:sprite + } +} + +tag:sprites { + ident:name ptr:image int:gridx int:gridy * { + ident:name int:x int:y int:w int:h * + } +} diff --git a/default.bam b/default.bam index 01128ae2e..956dbbafc 100644 --- a/default.bam +++ b/default.bam @@ -31,7 +31,60 @@ function Copy(outputdir, ...) return outputs end --- +function dc_header(output, data, script) + print("dc_header " .. PathFilename(output) .. " = " .. PathFilename(data) .. " ~ " .. PathFilename(script)) + return os.execute("scripts/compiler.py " .. data .. " " .. script .. " -h " .. output) +end + +function dc_source(output, data, script) + print("dc_source " .. PathFilename(output) .. " = " .. PathFilename(data) .. " ~ " .. PathFilename(script)) + return os.execute("scripts/compiler.py " .. data .. " " .. script .. " -s " .. output) +end + +function dc_data(output, data, script) + print("dc_data " .. PathFilename(output) .. " = " .. PathFilename(data) .. " ~ " .. PathFilename(script)) + return os.execute("scripts/compiler.py " .. data .. " " .. script .. " -d " .. output) +end + + +function DataCompile_Header(datafile, scriptfile, outputfile) + datafile = Path(datafile) + scriptfile = Path(scriptfile) + outputfile = Path(outputfile) + bam_add_job("dc_header", outputfile, datafile, scriptfile) + bam_add_dependency(outputfile, datafile) + bam_add_dependency(outputfile, scriptfile) + return outputfile +end + +function DataCompile_Source(datafile, scriptfile, outputfile) + datafile = Path(datafile) + scriptfile = Path(scriptfile) + outputfile = Path(outputfile) + bam_add_job("dc_source", outputfile, datafile, scriptfile) + bam_add_dependency(outputfile, datafile) + bam_add_dependency(outputfile, scriptfile) + return outputfile +end + +function DataCompile(datafile, scriptfile, headerfile, sourcefile, outputdatafile) + datafile = Path(datafile) + scriptfile = Path(scriptfile) + headerfile = Path(headerfile) + sourcefile = Path(sourcefile) + outputdatafile = Path(outputdatafile) + bam_add_job("dc_source", sourcefile, datafile, scriptfile) + bam_add_job("dc_header", headerfile, datafile, scriptfile) + bam_add_job("dc_data", outputdatafile, datafile, scriptfile) + bam_add_dependency(sourcefile, datafile) + bam_add_dependency(sourcefile, scriptfile) + bam_add_dependency(sourcefile, headerfile) + bam_add_dependency(headerfile, datafile) + bam_add_dependency(headerfile, scriptfile) + bam_add_dependency(outputdatafile, datafile) + bam_add_dependency(outputdatafile, scriptfile) + return {data = outputdatafile, header=headerfile, source=sourcefile} +end --baselib = Import("src/baselib/baselib.bam") baselib = Import("../baselib/baselib.bam") @@ -47,18 +100,25 @@ settings.cc.flags = "-Wall" settings.cc.includes:add("src") settings.cc.includes:add("../baselib/src/external/zlib") +serverdata = DataCompile("datasrc/teewars.ds", "datasrc/server.dts", "src/game/server/data.h", "src/game/server/data/data.cpp", "data/server.dat") +clientdata = DataCompile("datasrc/teewars.ds", "datasrc/client.dts", "src/game/client/data.h", "src/game/client/data/data.cpp", "data/client.dat") + engine = Compile(settings, Collect("src/engine/*.cpp")) client = Compile(settings, Collect("src/engine/client/*.cpp", "src/engine/client/pnglite/*.c")) server = Compile(settings, Collect("src/engine/server/*.cpp")) game_shared = Compile(settings, Collect("src/game/*.cpp")) -game_client = Compile(settings, Collect("src/game/client/*.cpp")) -game_server = Compile(settings, Collect("src/game/server/*.cpp")) +game_client = Compile(settings, Collect("src/game/client/*.cpp"), clientdata.source) +game_server = Compile(settings, Collect("src/game/server/*.cpp"), serverdata.source) editor = Compile(settings, Collect("src/editor/*.cpp")) +crapnet = Compile(settings, Collect("src/crapnet/*.cpp")) + client_exe = Link(settings, "teewars", engine, client, game_shared, game_client) server_exe = Link(server_settings, "teewars_srv", engine, server, game_shared, game_server) -- editor_exe = Link(settings, "editor", engine, game_shared, editor) +crapnet_exe = Link(server_settings, "crapnet", crapnet) -Target(PseudoTarget("client", client_exe)) -Target(PseudoTarget("server", server_exe)) +Target(PseudoTarget("client", client_exe, clientdata.data)) +Target(PseudoTarget("server", server_exe, serverdata.data)) +Target(PseudoTarget("tools", crapnet_exe)) -- Target(PseudoTarget("editor", editor_exe)) diff --git a/scripts/compiler.py b/scripts/compiler.py new file mode 100755 index 000000000..be7e4ce02 --- /dev/null +++ b/scripts/compiler.py @@ -0,0 +1,598 @@ +#!/usr/bin/python + +import sys +import struct + +option_ptrsize = struct.calcsize("P") +option_intsize = struct.calcsize("l") +option_floatsize = struct.calcsize("f") +option_inttype = "long" +option_floattype = "float" + +class node: + def __init__(self): + self.values = [] + self.children = [] + self.parent = 0 + + def name(self): + if len(self.values): + return self.values[0] + return "" + + def debug_print(self, level): + print (" "*level) + " ".join(self.values), + if len(self.children): + print "{" + for c in self.children: + c.debug_print(level+1) + print (" "*level)+"}" + else: + print "" + + def debug_root(self): + for c in self.children: + c.debug_print(0) + + # TODO: should return list of items in the tree, + def gather(self, str): + def recurse(parts, path, node): + if not len(parts): + r = {} + path = path + "." + node.values[0] + r = [node] + #print "found", path + return r + + l = [] + for c in node.children: + if parts[0] == "*" or c.values[0] == parts[0]: + if len(node.values): + if len(path): + l += recurse(parts[1:], path+"."+node.values[0], c) + else: + l += recurse(parts[1:], node.values[0], c) + else: + l += recurse(parts[1:], path, c) + return l + + parts = str.split(".") + return recurse(parts, "", self) + + def find_node(self, str): + parts = str.split(".") + node = self + for part in parts: + if len(part) == 0: + continue + if part == "parent": + node = node.parent + else: + found = 0 + for c in node.children: + if part == c.values[0]: + node = c + found = 1 + break + + if node == self: + return + return node + + def get_single(self, str): + parts = str.split("@") + index = -1 + if len(parts) == 2: + index = int(parts[1]) + + node = self + if len(parts[0]): + node = self.find_node(parts[0]) + + if not node: + print "failed to get", str + return Null + + if index == -1: + return node.get_path()[1:] + return node.values[index] + + def get_path(self): + if self.parent == 0: + return "" + return self.parent.get_path() + "." + self.values[0] + + def get_single_name(self, str): + return self.get_path()[1:] + "." + str + +class parser: + lines = [] + + def parse_node(self, this_node): + while len(self.lines): + line = self.lines.pop(0) # grab line + + fields = line.strip().split() # TODO: improve this to handle strings with spaces + if not len(fields): + continue + + if fields[-1] == '{': + new_node = node() + new_node.parent = this_node + new_node.values = fields[:-1] + this_node.children += [new_node] + self.parse_node(new_node) + elif fields[-1] == '}': + break + else: + new_node = node() + new_node.parent = this_node + new_node.values = fields + this_node.children += [new_node] + + def parse_file(self, filename): + self.lines = file(filename).readlines() + n = node() + self.parse_node(n) + return n + +def parse_file(filename): + return parser().parse_file(filename) + +class pointer: + def __init__(self, index, target): + self.index = index + self.target = target + +class data_constructor: + def __init__(self): + self.data = "" + self.trans = 0 + self.pointers = [] + self.targets = {} + + def get_type(self, s): + return self.trans.types[s] + + def allocate(self, size): + index = len(self.data) + self.data += "\0"*size + return index + + def add_pointer(self, index, target): + self.pointers += [pointer(index, target)] + + def add_target(self, target, index): + # TODO: warn about duplicates + #print "add_target(target='%s' index=%d)" % (target, index) + self.targets[target] = index + + def write(self, index, size, data): + try: + self.data = self.data[:index] + data + self.data[index+size:] + except: + print "write error:" + print "\tself.data =", self.data + print "\tdata =", data + + def patch_pointers(self): + for p in self.pointers: + if p.target in self.targets: + i = self.targets[p.target] + #print "ptr @ %d -> %s -> %d" % (p.index, p.target, i) + self.write(p.index, 8, struct.pack("P", i)) # TODO: fix me + else: + print "ERROR: couldn't find target '%s' for pointer at %d" % (p.target, p.index) + +class type: + def __init__(self): + self.name = "" + + def size(self): + pass + +class structure: + def __init__(self): + self.name = "" + self.members = [] + + def size(self): + s = 0 + for m in self.members: + s += m.size() + return s + + def emit_header_code(self, out): + print >>out, "struct", self.name + print >>out, "{" + for m in self.members: + for l in m.get_code(): + print >>out, "\t" + l + print >>out, "};" + print >>out, "" + + def emit_source_code(self, out): + print >>out, "static void patch_ptr_%s(%s *self, char *base)" % (self.name, self.name) + print >>out, "{" + for m in self.members: + for l in m.get_patch_code("self", "base"): + print >>out, "\t" + l + print >>out, "}" + print >>out, "" + + def emit_data(self, cons, index, src_data): + #print self.name+":" + member_index = index + for m in self.members: + #print "\t" + m.name + m.emit_data(cons, member_index, src_data) + member_index += m.size() + +class variable: + def __init__(self): + self.expr = "" + self.type = "" + self.subtype = "" + + def get_code(self): + return [] + + def get_patch_code(self, ptrname, basename): + return [] + + def emit_data(self, cons, index, src_data): + pass + +class variable_int(variable): + def get_code(self): + return ["%s %s;" % (option_inttype, self.name)] + def size(self): + return option_intsize + def emit_data(self, cons, index, src_data): + try: + value = int(self.expr) + except: + value = int(src_data.get_single(self.expr)) + #print "int", self.name, "=", value, "@", index + data = struct.pack("l", value) + cons.write(index, len(data), data) + +class variable_float(variable): + def get_code(self): + return ["%s %s;" % (option_floattype, self.name)] + def size(self): + return option_floatsize + def emit_data(self, cons, index, src_data): + try: + value = float(self.expr) + except: + value = float(src_data.get_single(self.expr)) + #print "int", self.name, "=", value, "@", index + data = struct.pack("f", value) + cons.write(index, len(data), data) + +class variable_string(variable): + def get_code(self): + return ["char *%s;" % (self.name)] + def get_patch_code(self, ptrname, basename): + return ["patch_ptr((char **)&(%s->%s), %s);" % (ptrname, self.name, basename)] + def size(self): + return option_ptrsize + def emit_data(self, cons, index, src_data): + string = src_data.get_single(self.expr) + string = string.strip()[1:-1] # skip " and " + + string_index = cons.allocate(len(string)+1) + cons.write(string_index, len(string), string) + + data = struct.pack("P", string_index) # TODO: solve this + cons.write(index, len(data), data) + +class variable_ptr(variable): + def get_code(self): + return ["%s *%s;" % (self.subtype, self.name)] + def get_patch_code(self, ptrname, basename): + return ["patch_ptr((char**)&(%s->%s), %s);" % (ptrname, self.name, basename)] + def size(self): + return option_ptrsize + def emit_data(self, cons, index, src_data): + target = src_data.get_single(self.expr) + cons.add_pointer(index, target) + +class variable_instance(variable): + def get_code(self): + return ["%s %s;" % (self.subtype, self.name)] + def get_patch_code(self, ptrname, basename): + return ["patch_ptr_%s(&(%s->%s), %s);" % (self.subtype, ptrname, self.name, basename)] + def size(self): + return translator.types[self.subtype].size() + def emit_data(self, cons, index, src_data): + print self.expr + target = src_data.find_node(self.expr) + print target + translator.types[self.subtype].emit_data(cons, index, target) + #target = + #cons.add_pointer(index, target) + +class variable_array(variable): + def get_code(self): + return ["long num_%s;" % self.name, + "%s *%s;" % (self.subtype, self.name)] + def get_patch_code(self, ptrname, baseptr): + code = [] + code += ["patch_ptr((char**)&(%s->%s), %s);" % (ptrname, self.name, baseptr)] + code += ["for(int i = 0; i < %s->num_%s; i++)" % (ptrname, self.name)] + code += ["\tpatch_ptr_%s(%s->%s+i, %s);" % (self.subtype, ptrname, self.name, baseptr)] + return code + def emit_data(self, cons, index, src_data): + array_data = src_data.gather(self.expr) + array_type = cons.get_type(self.subtype) + size = array_type.size()*len(array_data) + + #print "packing array", self.name + #print "\ttype =", array_type.name + #print "\tsize =", array_type.size() + array_index = cons.allocate(size) + data = struct.pack("lP", len(array_data), array_index) # TODO: solve this + cons.write(index, len(data), data) + + member_index = array_index + for node in array_data: + cons.add_target(node.get_path()[1:], member_index) + array_type.emit_data(cons, member_index, node) + member_index += array_type.size() + #print "array", member_index + + def size(self): + return option_ptrsize+option_intsize + +class const_arrayint: + def __init__(self): + self.name = "" + self.values = [] + + def emit_header_code(self, out): + print >>out, "enum" + print >>out, "{" + for i in xrange(0, len(self.values)): + print >>out, "\t%s_%s = %d," % (self.name.upper(), self.values[i].upper(), i) + + print >>out, "\tNUM_%sS = %d" % (self.name.upper(), len(self.values)) + print >>out, "};" + print >>out, "" + +class translator: + def __init__(self): + self.types = {} + self.structs = [] + self.constants = [] + self.data = 0 + self.srcdata = 0 + + self.types["int"] = variable_int() + self.types["float"] = variable_float() + self.types["string"] = variable_string() + self.types["ptr"] = variable_ptr() + self.types["array"] = variable_array() + + def parse_variable(self, node): + if len(node.values) != 4: + print node.values + raise "error parsing variable" + + type = node.values[0] + subtype = "" + if type == "int": + v = variable_int() + elif type == "float": + v = variable_float() + elif type == "string": + v = variable_string() + else: + # complex type + parts = type.split(":") + if len(parts) != 2: + raise "can't emit code for variable %s of type %s" % (self.name, self.type) + elif parts[0] == "ptr": + subtype = parts[1] + v = variable_ptr() + elif parts[0] == "instance": + subtype = parts[1] + v = variable_instance() + elif parts[0] == "array": + subtype = parts[1] + v = variable_array() + else: + raise "can't emit code for variable %s of type %s" % (self.name, self.type) + + v.translator = self + v.type = node.values[0] + v.subtype = subtype + v.name = node.values[1] + assignment = node.values[2] + v.expr = node.values[3] + if assignment != "=": + raise "error parsing variable. expected =" + return v + + def parse_struct(self, node): + if len(node.values) != 2: + raise "error parsing struct" + s = structure() + s.name = node.values[1] + + for statement in node.children: + s.members += [self.parse_variable(statement)] + return s + + def parse_constant(self, node): + if len(node.values) != 5: + print node.values + raise "error parsing constant" + + type = node.values[1] + name = node.values[2] + assignment = node.values[3] + expression = node.values[4] + + if assignment != "=": + print node.values + raise "error parsing constant" + + ints = const_arrayint() + ints.name = name + + items = self.srcdata.gather(expression) + for c in items: + ints.values += [c.name()] + self.constants += [ints] + + def parse(self, script, srcdata): + self.srcdata = srcdata + for statement in script.children: + if statement.values[0] == "struct": + s = self.parse_struct(statement) + self.structs += [s] + self.types[s.name] = s + elif statement.values[0] == "const": + self.parse_constant(statement) + else: + raise "unknown statement:" + statement + + def emit_header_code(self, out): + for c in self.constants: + c.emit_header_code(out) + + for s in self.structs: + s.emit_header_code(out) + print >>out, "" + print >>out, "data_container *load_data_container(const char *filename);" + print >>out, "" + + + def emit_source_code(self, out): + print >>out, ''' + +#include "../data.h" +#include +#include + +void patch_ptr(char **ptr, char *base) +{ + *ptr = base+(size_t)(*ptr); +} +''' + + for s in self.structs: + s.emit_source_code(out) + print >>out, ''' +data_container *load_data_container(const char *filename) +{ + data_container *con = 0; + int size; + + /* open file */ + FILE *f = fopen(filename, "rb"); + + /* get size */ + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + + /* allocate, read data and close file */ + con = (data_container*)malloc(size); + fread(con, 1, size, f); + fclose(f); + + /* patch all pointers */ + patch_ptr_data_container(con, (char *)con); + return con; +} + +''' + + def emit_data(self): + for s in self.structs: + if s.name == "data_container": + #print "found data_container" + cons = data_constructor() + cons.trans = self + i = cons.allocate(s.size()) + s.emit_data(cons, i, self.srcdata) + cons.patch_pointers() + return cons.data + +def create_translator(script, srcdata): + t = translator() + t.parse(script, srcdata) + return t + +def validate(script, validator): + def validate_values(values, check): + if not len(check) or check[0] == "*": + print "too many values" + return + p = check[0].split(":") + type = p[0] + name = p[1] + + # TODO: check type and stuff + + # recurse + if len(values) > 1: + if not len(check): + print "unexpected value" + validate_values(values[1:], check[1:]) + else: + if len(check) > 1 and check[1] != "*": + print "to few values" + + if len(script.values): + validate_values(script.values, validator.values) + + for child in script.children: + tag = child.values[0] + n = validator.find_node("tag:"+tag) + if not n: + found = 0 + for vc in validator.children: + if "ident:" in vc.values[0]: + validate(child, vc) + print vc.values[0] + found = 1 + break + + if not found: + print "error:", tag, "not found" + else: + print "tag:"+tag + validate(child, n) + +input_filename = sys.argv[1] +script_filename = sys.argv[2] + +output_filename = 0 +header_filename = 0 +source_filename = 0 + +if sys.argv[3] == '-h': + header_filename = sys.argv[4] +elif sys.argv[3] == '-s': + source_filename = sys.argv[4] +elif sys.argv[3] == '-d': + output_filename = sys.argv[4] + +srcdata = parse_file(input_filename) +script = parse_file(script_filename) + +translator = create_translator(script, srcdata) + +if header_filename: + translator.emit_header_code(file(header_filename, "w")) +if source_filename: + translator.emit_source_code(file(source_filename, "w")) + +if output_filename: + rawdata = translator.emit_data() + file(output_filename, "wb").write(rawdata) + #print "filesize:", len(rawdata) diff --git a/src/crapnet/crapnet.cpp b/src/crapnet/crapnet.cpp new file mode 100644 index 000000000..b441a3504 --- /dev/null +++ b/src/crapnet/crapnet.cpp @@ -0,0 +1,126 @@ +#include +#include + +#include + +using namespace baselib; + +struct packet +{ + packet *prev; + packet *next; + + netaddr4 send_to; + int64 timestamp; + int id; + int data_size; + char data[1]; +}; + +static packet *first = (packet *)0; +static packet *last = (packet *)0; +static int current_latency = 0; +static int debug = 0; + +int run(int port, netaddr4 dest) +{ + netaddr4 src(0,0,0,0,0); + socket_udp4 socket; + socket.open(port); + char buffer[1024*2]; + int id = 0; + + while(1) + { + // handle incomming packets + while(1) + { + // fetch data + netaddr4 from; + int bytes = socket.recv(&from, buffer, 1024*2); + if(bytes <= 0) + break; + + // create new packet + packet *p = (packet *)mem_alloc(sizeof(packet)+bytes, 1); + + if(from == dest) + p->send_to = src; + else + { + src = from; + p->send_to = dest; + } + + // queue packet + p->prev = last; + p->next = 0; + if(last) + last->next = p; + else + { + first = p; + last = p; + } + last = p; + + // set data in packet + p->timestamp = time_get(); + p->data_size = bytes; + p->id = id++; + mem_copy(p->data, buffer, bytes); + + if(debug) + dbg_msg("crapnet", "<< %08d %d.%d.%d.%d:%5d (%d)", p->id, from.ip[0], from.ip[1], from.ip[2], from.ip[3], from.port, p->data_size); + } + + // + while(1) + { + //dbg_msg("crapnet", "%p", first); + if(first && (time_get()-first->timestamp) > current_latency) + { + packet *p = first; + first = first->next; + if(first) + first->prev = 0; + else + last = 0; + + if(debug) + { + dbg_msg("crapnet", ">> %08d %d.%d.%d.%d:%5d (%d)", p->id, + p->send_to.ip[0], p->send_to.ip[1], + p->send_to.ip[2], p->send_to.ip[3], + p->send_to.port, p->data_size); + } + + // send and remove packet + if((rand()%10) != 0) // heavy packetloss + socket.send(&p->send_to, p->data, p->data_size); + + // update lag + double flux = rand()/(double)RAND_MAX; + int ms_spike = 250; + int ms_flux = 100; + int ms_ping = 50; + current_latency = ((time_freq()*ms_ping)/1000) + (int64)(((time_freq()*ms_flux)/1000)*flux); // 50ms + + if((p->id%100) == 0) + current_latency += (time_freq()*ms_spike)/1000; + + mem_free(p); + } + else + break; + } + + thread_sleep(1); + } +} + +int main(int argc, char **argv) +{ + run(8302, netaddr4(127,0,0,1,8303)); + return 0; +} diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 86cc044d5..4fb869cd0 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -13,59 +14,14 @@ #include #include "ui.h" -#include +#include #include #include +#include using namespace baselib; -// --- string handling (MOVE THESE!!) --- -void snap_encode_string(const char *src, int *dst, int length, int max_length) -{ - const unsigned char *p = (const unsigned char *)src; - - // handle whole int - for(int i = 0; i < length/4; i++) - { - *dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]); - p += 4; - dst++; - } - - // take care of the left overs - int left = length%4; - if(left) - { - unsigned last = 0; - switch(left) - { - case 3: last |= p[2]<<8; - case 2: last |= p[1]<<16; - case 1: last |= p[0]<<24; - } - *dst = last; - } -} - -void snap_decode_string(const int *src, char *dst, int max_length) -{ - dbg_assert((max_length%4) == 0, "length must be power of 4"); - for(int i = 0; i < max_length; i++) - dst[0] = 0; - - for(int i = 0; i < max_length/4; i++) - { - dst[0] = (*src>>24)&0xff; - dst[1] = (*src>>16)&0xff; - dst[2] = (*src>>8)&0xff; - dst[3] = (*src)&0xff; - src++; - dst+=4; - } - dst[-1] = 0; // make sure to zero terminate -} - // --- input wrappers --- static int keyboard_state[2][input::last]; static int keyboard_current = 0; @@ -92,7 +48,7 @@ void inp_update() } // --- input snapping --- -static int input_data[MAX_INPUT_SIZE]; +static int input_data[MAX_INPUT_SIZE] = {0}; static int input_data_size; static int input_is_changed = 1; void snap_input(void *data, int size) @@ -110,6 +66,8 @@ enum NUM_SNAPSHOT_TYPES=3, }; +static snapshot_storage snapshots_new; +static int current_tick; static snapshot *snapshots[NUM_SNAPSHOT_TYPES]; static char snapshot_data[NUM_SNAPSHOT_TYPES][MAX_SNAPSHOT_SIZE]; static int recived_snapshots; @@ -127,7 +85,7 @@ void *snap_get_item(int snapid, int index, snap_item *item) snapshot::item *i = snapshots[snapid]->get_item(index); item->type = i->type(); item->id = i->id(); - return (void *)i->data; + return (void *)i->data(); } int snap_num_items(int snapid) @@ -145,11 +103,16 @@ static void snap_init() recived_snapshots = 0; } -float snap_intratick() +float client_intratick() { return (time_get() - snapshot_start_time)/(float)(time_freq()/SERVER_TICK_SPEED); } +int client_tick() +{ + return current_tick; +} + void *snap_find_item(int snapid, int type, int id) { // TODO: linear search. should be fixed. @@ -157,123 +120,45 @@ void *snap_find_item(int snapid, int type, int id) { snapshot::item *itm = snapshots[snapid]->get_item(i); if(itm->type() == type && itm->id() == id) - return (void *)itm->data; + return (void *)itm->data(); } return 0x0; } -int menu_loop(); -float frametime = 0.0001f; +int menu_loop(); // TODO: what is this? +static float frametime = 0.0001f; float client_frametime() { return frametime; } -void unpack(const char *src, const char *fmt, ...) +static net_client net; + +int client_send_msg() { + const msg_info *info = msg_get_info(); + NETPACKET packet; + packet.client_id = 0; + packet.data = info->data; + packet.data_size = info->size; + + if(info->flags&MSGFLAG_VITAL) + packet.flags = PACKETFLAG_VITAL; + + net.send(&packet); + return 0; } -/*int modc_onmsg(int msg) -{ - msg_get("iis") -}*/ - -/* - i = int (int i) - s = string (const char *str) - r = raw data (int size, void *data) -*/ - -/* -class packet2 -{ -private: - // packet data - struct header - { - unsigned msg; - unsigned ack; - unsigned seq; - }; - - unsigned char packet_data[MAX_PACKET_SIZE]; - unsigned char *current; - - enum - { - MAX_PACKET_SIZE = 1024, - }; - -public: - packet2() - { - current = packet_data; - current += sizeof(header); - } - - int pack(char *dst, const char *fmt, ...) - { - va_list arg_list; - va_start(arg_list, fmt); - while(*fmt) - { - if(*fmt == 's') - { - // pack string - const char *s = va_arg(arg_list, const char*); - *dst++ = 2; - while(*s) - { - *dst = *s; - dst++; - s++; - } - *dst = 0; // null terminate - dst++; - fmt++; - } - else if(*fmt == 'i') - { - // pack int - int i = va_arg(arg_list, int); - *dst++ = 1; - *dst++ = (i>>24)&0xff; - *dst++ = (i>>16)&0xff; - *dst++ = (i>>8)&0xff; - *dst++ = i&0xff; - fmt++; - } - else - { - dbg_break(); // error - break; - } - } - va_end(arg_list); - } -}; -*/ -/* -int msg_get(const char *fmt) -{ - -} - -int client_msg_send(int msg, const char *fmt, ...) - -int server_msg_send(int msg, const char *fmt, ...) -{ - -}*/ - // --- client --- +// TODO: remove this class class client { public: - socket_udp4 socket; - connection conn; + + //socket_udp4 socket; + //connection conn; int64 reconnect_timer; int snapshot_part; @@ -305,37 +190,24 @@ public: void set_fullscreen(bool flag) { fullscreen = flag; } - void send_packet(packet *p) - { - conn.send(p); - } - - void send_connect() + void send_info() { recived_snapshots = 0; - - /* - pack(NETMSG_CLIENT_CONNECT, "sssss", - TEEWARS_NETVERSION, - name, - "no clan", - "password", - "myskin"); - */ - - packet p(NETMSG_CLIENT_CONNECT); - p.write_str(TEEWARS_VERSION); // payload - p.write_str(config.player_name); - p.write_str("no clan"); - p.write_str("password"); - p.write_str("myskin"); - send_packet(&p); + + msg_pack_start(NETMSG_INFO, MSGFLAG_VITAL); + msg_pack_string(config.player_name, 128); + msg_pack_string(config.clan_name, 128); + msg_pack_string(config.password, 128); + msg_pack_string("myskin", 128); + msg_pack_end(); + client_send_msg(); } - void send_done() + void send_entergame() { - packet p(NETMSG_CLIENT_DONE); - send_packet(&p); + msg_pack_start(NETMSG_ENTERGAME, MSGFLAG_VITAL); + msg_pack_end(); + client_send_msg(); } void send_error(const char *error) @@ -343,40 +215,38 @@ public: /* pack(NETMSG_CLIENT_ERROR, "s", error); */ + /* packet p(NETMSG_CLIENT_ERROR); p.write_str(error); send_packet(&p); //send_packet(&p); //send_packet(&p); + */ } void send_input() { - /* - pack(NETMSG_CLIENT_ERROR, "s", error); - */ - packet p(NETMSG_CLIENT_INPUT); - p.write_int(input_data_size); + msg_pack_start(NETMSG_INPUT, 0); + msg_pack_int(input_data_size); for(int i = 0; i < input_data_size/4; i++) - p.write_int(input_data[i]); - send_packet(&p); + msg_pack_int(input_data[i]); + msg_pack_end(); + client_send_msg(); } void disconnect() { + /* send_error("disconnected"); set_state(STATE_OFFLINE); map_unload(); + */ } void connect(netaddr4 *server_address) { - conn.init(&socket, server_address); - - // start by sending connect - send_connect(); + net.connect(server_address); set_state(STATE_CONNECTING); - reconnect_timer = time_get()+time_freq(); } bool load_data() @@ -385,14 +255,42 @@ public: return true; } + void debug_render() + { + gfx_blend_normal(); + gfx_texture_set(debug_font); + gfx_mapscreen(0,0,gfx_screenwidth(),gfx_screenheight()); + + static NETSTATS prev, current; + static int64 last_snap = 0; + if(time_get()-last_snap > time_freq()/10) + { + last_snap = time_get(); + prev = current; + net.stats(¤t); + } + + char buffer[512]; + sprintf(buffer, "send: %8d recv: %8d", + (current.send_bytes-prev.send_bytes)*10, + (current.recv_bytes-prev.recv_bytes)*10); + gfx_quads_text(10, 10, 16, buffer); + + } + void render() { gfx_clear(0.0f,0.0f,0.0f); // this should be moved around abit + // TODO: clean this shit up! if(get_state() == STATE_ONLINE) { modc_render(); + + // debug render stuff + debug_render(); + } else if (get_state() != STATE_CONNECTING && get_state() != STATE_LOADING) { @@ -404,7 +302,7 @@ public: else if (status) connect(&server_address); } - else if (get_state() == STATE_CONNECTING) + else if (get_state() == STATE_CONNECTING || get_state() == STATE_LOADING) { static int64 start = time_get(); static int tee_texture; @@ -458,15 +356,18 @@ public: // init menu modmenu_init(); + net.open(0); + // open socket + /* if(!socket.open(0)) { dbg_msg("network/client", "failed to open socket"); return; - } + }*/ // connect to the server if wanted - if (server_address) + if(server_address) connect(server_address); //int64 inputs_per_second = 50; @@ -504,6 +405,9 @@ public: input::set_mouse_mode(input::mode_absolute); if(input::pressed(input::f2)) input::set_mouse_mode(input::mode_relative); + + if(input::pressed(input::lctrl) && input::pressed('Q')) + break; // pump the network pump_network(); @@ -523,13 +427,13 @@ public: if(reporttime < time_get()) { - unsigned sent, recved; - conn.counter_get(&sent, &recved); - dbg_msg("client/report", "fps=%.02f", - frames/(float)(reportinterval/time_freq())); + //unsigned sent, recved; + //conn.counter_get(&sent, &recved); + dbg_msg("client/report", "fps=%.02f netstate=%d", + frames/(float)(reportinterval/time_freq()), net.state()); frames = 0; reporttime += reportinterval; - conn.counter_reset(); + //conn.counter_reset(); } if (input::pressed(input::esc)) @@ -556,117 +460,171 @@ public: set_state(STATE_BROKEN); } - void process_packet(packet *p) + void process_packet(NETPACKET *packet) { - if(p->version() != TEEWARS_NETVERSION) + int msg = msg_unpack_start(packet->data, packet->data_size); + if(msg == NETMSG_MAP) { - error("wrong version"); - } - else if(p->msg() == NETMSG_SERVER_ACCEPT) - { - const char *map; - map = p->read_str(); + const char *map = msg_unpack_string(); + dbg_msg("client/network", "connection accepted, map=%s", map); + set_state(STATE_LOADING); - if(p->is_good()) + if(map_load(map)) { - dbg_msg("client/network", "connection accepted, map=%s", map); - set_state(STATE_LOADING); - - if(map_load(map)) - { - modc_entergame(); - send_done(); - dbg_msg("client/network", "loading done"); - // now we will wait for two snapshots - // to finish the connection - } - else - { - error("failure to load map"); - } + modc_entergame(); + send_entergame(); + dbg_msg("client/network", "loading done"); + // now we will wait for two snapshots + // to finish the connection + } + else + { + error("failure to load map"); } } - else if(p->msg() == NETMSG_SERVER_SNAP) + else if(msg == NETMSG_SNAP || msg == NETMSG_SNAPSMALL || msg == NETMSG_SNAPEMPTY) { //dbg_msg("client/network", "got snapshot"); - int num_parts = p->read_int(); - int part = p->read_int(); - int part_size = p->read_int(); + int game_tick = msg_unpack_int(); + int delta_tick = game_tick-msg_unpack_int(); + int num_parts = 1; + int part = 0; + int part_size = 0; - if(p->is_good()) + if(msg == NETMSG_SNAP) { - if(snapshot_part == part) + num_parts = msg_unpack_int(); + part = msg_unpack_int(); + } + + if(msg != NETMSG_SNAPEMPTY) + part_size = msg_unpack_int(); + + if(snapshot_part == part) + { + // TODO: clean this up abit + const char *d = (const char *)msg_unpack_raw(part_size); + mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size); + snapshot_part++; + + if(snapshot_part == num_parts) { - const char *d = p->read_raw(part_size); - mem_copy((char*)snapshots[SNAP_INCOMMING] + part*MAX_SNAPSHOT_PACKSIZE, d, part_size); - snapshot_part++; - - if(snapshot_part == num_parts) - { - snapshot *tmp = snapshots[SNAP_PREV]; - snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; - snapshots[SNAP_CURRENT] = tmp; + snapshot *tmp = snapshots[SNAP_PREV]; + snapshots[SNAP_PREV] = snapshots[SNAP_CURRENT]; + snapshots[SNAP_CURRENT] = tmp; + current_tick = game_tick; - // decompress snapshot - lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]); - - // apply snapshot, cycle pointers - recived_snapshots++; - snapshot_start_time = time_get(); - - // we got two snapshots until we see us self as connected - if(recived_snapshots == 2) - { - local_start_time = time_get(); - set_state(STATE_ONLINE); - } - - if(recived_snapshots > 2) - modc_newsnapshot(); - - snapshot_part = 0; + // decompress snapshot + void *deltadata = snapshot_empty_delta(); + int deltasize = sizeof(int)*3; + + unsigned char tmpbuffer[MAX_SNAPSHOT_SIZE]; + unsigned char tmpbuffer2[MAX_SNAPSHOT_SIZE]; + if(part_size) + { + //int snapsize = lzw_decompress(snapshots[SNAP_INCOMMING], snapshots[SNAP_CURRENT]); + int compsize = zerobit_decompress(snapshots[SNAP_INCOMMING], part_size, tmpbuffer); + //int compsize = lzw_decompress(snapshots[SNAP_INCOMMING],tmpbuffer); + int intsize = intpack_decompress(tmpbuffer, compsize, tmpbuffer2); + deltadata = tmpbuffer2; + deltasize = intsize; } - } - else - { - dbg_msg("client", "snapshot reset!"); + // find snapshot that we should use as delta + static snapshot emptysnap; + emptysnap.data_size = 0; + emptysnap.num_items = 0; + + snapshot *deltashot = &emptysnap; + int deltashot_size; + + if(delta_tick >= 0) + { + void *delta_data; + deltashot_size = snapshots_new.get(delta_tick, &delta_data); + if(deltashot_size >= 0) + { + deltashot = (snapshot *)delta_data; + } + else + { + // TODO: handle this + dbg_msg("client", "error, couldn't find the delta snapshot"); + } + } + + int snapsize = snapshot_unpack_delta(deltashot, (snapshot*)snapshots[SNAP_CURRENT], deltadata, deltasize); + //snapshot *shot = (snapshot *)snapshots[SNAP_CURRENT]; + + // purge old snapshots + snapshots_new.purge_until(delta_tick); + snapshots_new.purge_until(game_tick-50); // TODO: change this to server tickrate + + // add new + snapshots_new.add(game_tick, snapsize, snapshots[SNAP_CURRENT]); + + // apply snapshot, cycle pointers + recived_snapshots++; + snapshot_start_time = time_get(); + + // we got two snapshots until we see us self as connected + if(recived_snapshots == 2) + { + local_start_time = time_get(); + set_state(STATE_ONLINE); + } + + if(recived_snapshots > 2) + modc_newsnapshot(); + snapshot_part = 0; + + // ack snapshot + msg_pack_start(NETMSG_SNAPACK, 0); + msg_pack_int(game_tick); + msg_pack_end(); + client_send_msg(); } } - } - else - { - dbg_msg("server/client", "unknown packet %x", p->msg()); + else + { + dbg_msg("client", "snapshot reset!"); + snapshot_part = 0; + } } } void pump_network() { - while(1) + net.update(); + + // check for errors + if(get_state() != STATE_OFFLINE && net.state() == NETSTATE_OFFLINE) { - packet p; - netaddr4 from; - int bytes = socket.recv(&from, p.data(), p.max_size()); - - if(bytes <= 0) - break; - - process_packet(&p); + // TODO: add message to the user there + set_state(STATE_OFFLINE); + } + + // + if(get_state() == STATE_CONNECTING && net.state() == NETSTATE_ONLINE) + { + // we switched to online + dbg_msg("client", "connected, sending info"); + set_state(STATE_LOADING); + send_info(); } - // - if(get_state() == STATE_CONNECTING && time_get() > reconnect_timer) - { - send_connect(); - reconnect_timer = time_get() + time_freq(); - } + // process packets + NETPACKET packet; + while(net.recv(&packet)) + process_packet(&packet); } }; int main(int argc, char **argv) { dbg_msg("client", "starting..."); + config_reset(); config_load("teewars.cfg"); @@ -683,9 +641,23 @@ int main(int argc, char **argv) { if(argv[i][0] == '-' && argv[i][1] == 'c' && argv[i][2] == 0 && argc - i > 1) { - // -c SERVER + // -c SERVER:PORT i++; - if(net_host_lookup(argv[i], 8303, &server_address) != 0) + const char *port_str = 0; + for(int k = 0; argv[i][k]; k++) + { + if(argv[i][k] == ':') + { + port_str = &(argv[i][k+1]); + argv[i][k] = 0; + break; + } + } + int port = 8303; + if(port_str) + port = atoi(port_str); + + if(net_host_lookup(argv[i], port, &server_address) != 0) dbg_msg("main", "could not find the address of %s, connecting to localhost", argv[i]); else connect_at_once = true; diff --git a/src/engine/lzw.cpp b/src/engine/compression.cpp similarity index 60% rename from src/engine/lzw.cpp rename to src/engine/compression.cpp index 80dd1c222..c878b15bd 100644 --- a/src/engine/lzw.cpp +++ b/src/engine/compression.cpp @@ -1,3 +1,4 @@ +#include #include // LZW Compressor @@ -221,3 +222,149 @@ long lzw_decompress(const void *src_, void *dst_) return 0; } + +// Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign +unsigned char *vint_pack(unsigned char *dst, int i) +{ + *dst = (i>>25)&0x40; // set sign bit if i<0 + i = i^(i>>31); // if(i<0) i = ~i + + *dst |= i&0x3F; // pack 6bit into dst + i >>= 6; // discard 6 bits + if(i) + { + *dst |= 0x80; // set extend bit + while(1) + { + dst++; + *dst = i&(0x7F); // pack 7bit + i >>= 7; // discard 7 bits + *dst |= (i!=0)<<7; // set extend bit (may branch) + if(!i) + break; + } + } + + dst++; + return dst; +} + +const unsigned char *vint_unpack(const unsigned char *src, int *i) +{ + int sign = (*src>>6)&1; + *i = *src&0x3F; + + while(1) + { + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7+7); + + if(!(*src&0x80)) break; + src++; + *i |= (*src&(0x7F))<<(6+7+7+7); + } + + src++; + *i ^= -sign; // if(sign) *i = ~(*i) + return src; +} + + +long intpack_decompress(const void *src_, int size, void *dst_) +{ + const unsigned char *src = (unsigned char *)src_; + const unsigned char *end = src + size; + int *dst = (int *)dst_; + while(src < end) + { + src = vint_unpack(src, dst); + dst++; + } + return (long)((unsigned char *)dst-(unsigned char *)dst_); +} + +long intpack_compress(const void *src_, int size, void *dst_) +{ + int *src = (int *)src_; + unsigned char *dst = (unsigned char *)dst_; + size /= 4; + while(size) + { + dst = vint_pack(dst, *src); + size--; + src++; + } + return (long)(dst-(unsigned char *)dst_); +} + + +long zerobit_compress(const void *src_, int size, void *dst_) +{ + unsigned char *src = (unsigned char *)src_; + unsigned char *dst = (unsigned char *)dst_; + + //int zerocount = 0 ; + while(size) + { + unsigned char bit = 0x80; + unsigned char mask = 0; + int dst_move = 1; + int chunk = size < 8 ? size : 8; + size -= chunk; + + for(int b = 0; b < chunk; b++, bit>>=1) + { + if(*src) + { + dst[dst_move] = *src; + mask |= bit; + dst_move++; + } + + src++; + } + + *dst = mask; + dst += dst_move; + } + + long l = (long)(dst-(unsigned char *)dst_); + //dbg_msg("zerobit", "%d", (int)l); + return l; +} + +long zerobit_decompress(const void *src_, int size, void *dst_) +{ + unsigned char *src = (unsigned char *)src_; + unsigned char *dst = (unsigned char *)dst_; + unsigned char *end = src + size; + + //int zerocount = 0 ; + while(src != end) + { + unsigned char bit = 0x80; + unsigned char mask = *src++; + + for(int b = 0; b < 8; b++, bit>>=1) + { + if(mask&bit) + *dst++ = *src++; + else + *dst++ = 0; + } + } + + long l = (long)(dst-(unsigned char *)dst_); + //dbg_msg("zerobit", "%d", (int)l); + return l; +} + diff --git a/src/engine/compression.h b/src/engine/compression.h new file mode 100644 index 000000000..d1f13d487 --- /dev/null +++ b/src/engine/compression.h @@ -0,0 +1,12 @@ +// lzw is no longer in use +long lzw_compress(const void *src, int size, void *dst); +long lzw_decompress(const void *src, void *dst); + +unsigned char *vint_pack(unsigned char *dst, int i); +const unsigned char *vint_unpack(const unsigned char *src, int *inout); + +long intpack_compress(const void *src, int size, void *dst); +long intpack_decompress(const void *src, int size, void *dst); + +long zerobit_compress(const void *src, int size, void *dst); +long zerobit_decompress(const void *src, int size, void *dst); diff --git a/src/engine/config.cpp b/src/engine/config.cpp index bedeb4d4a..7694b1591 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -29,7 +29,7 @@ void config_set(const char *line) const char *val_str = strchr(line, '='); if (val_str) { - memcpy(var_str, line, val_str - line); + mem_copy(var_str, line, val_str - line); var_str[val_str - line] = 0; ++val_str; diff --git a/src/engine/config_variables.h b/src/engine/config_variables.h index 852d26a96..27ca1931c 100644 --- a/src/engine/config_variables.h +++ b/src/engine/config_variables.h @@ -1,2 +1,9 @@ #include "../game/game_variables.h" +MACRO_CONFIG_INT(screen_width, 800, 0, 0) +MACRO_CONFIG_INT(screen_height, 600, 0, 0) +MACRO_CONFIG_STR(player_name, 32, "nameless tee") +MACRO_CONFIG_STR(clan_name, 32, "") +MACRO_CONFIG_STR(password, 32, "") + +MACRO_CONFIG_INT(sv_port, 8303, 0, 0) diff --git a/src/engine/interface.h b/src/engine/interface.h index c80992478..27f73e17e 100644 --- a/src/engine/interface.h +++ b/src/engine/interface.h @@ -520,7 +520,7 @@ void snap_input(void *data, int size); Remarks: DOCTODO: Explain how to use it. */ -float snap_intratick(); +//float snap_intratick(); /* Group: Server Callbacks @@ -681,8 +681,13 @@ void modmenu_shutdown(); */ int modmenu_render(void *server_address); -void snap_encode_string(const char *src, int *dst, int length, int max_length); -void snap_decode_string(const int *src, char *dst, int length); + + + + + +//void snap_encode_string(const char *src, int *dst, int length, int max_length); +//void snap_decode_string(const int *src, char *dst, int length); int server_getclientinfo(int client_id, client_info *info); int server_tick(); @@ -694,6 +699,44 @@ void inp_update(); float client_frametime(); float client_localtime(); +// message packing +enum +{ + MSGFLAG_VITAL=1, +}; + +void msg_pack_start(int msg, int flags); +void msg_pack_int(int i); +void msg_pack_string(const char *p, int limit); +void msg_pack_raw(const void *data, int size); +void msg_pack_end(); + +struct msg_info +{ + int msg; + int flags; + const unsigned char *data; + int size; +}; + +const msg_info *msg_get_info(); + +// message unpacking +int msg_unpack_start(const void *data, int data_size); +int msg_unpack_int(); +const char *msg_unpack_string(); +const unsigned char *msg_unpack_raw(int size); + +// message sending +int server_send_msg(int client_id); +int client_send_msg(); + +int client_tick(); +float client_intratick(); + + +int modc_message(); + #define MASTER_SERVER_ADDRESS "master.teewars.com" #define MASTER_SERVER_PORT 8300 diff --git a/src/engine/lzw.h b/src/engine/lzw.h deleted file mode 100644 index af29665e4..000000000 --- a/src/engine/lzw.h +++ /dev/null @@ -1,2 +0,0 @@ -long lzw_compress(const void *src, int size, void *dst); -long lzw_decompress(const void *src, void *dst); diff --git a/src/engine/msg.cpp b/src/engine/msg.cpp new file mode 100644 index 000000000..024bab2c7 --- /dev/null +++ b/src/engine/msg.cpp @@ -0,0 +1,43 @@ + +#include "interface.h" +#include "packet.h" + +// message packing +static data_packer packer; +static msg_info pack_info; + +void msg_pack_int(int i) { packer.add_int(i); } +void msg_pack_string(const char *p, int limit) { packer.add_string(p, limit); } +void msg_pack_raw(const void *data, int size) { packer.add_raw((const unsigned char *)data, size); } + +void msg_pack_start(int msg, int flags) +{ + packer.reset(); + pack_info.msg = msg; + pack_info.flags = flags; + + msg_pack_int(msg); +} + +void msg_pack_end() +{ + pack_info.size = packer.size(); + pack_info.data = packer.data(); +} + +const msg_info *msg_get_info() +{ + return &pack_info; +} + +// message unpacking +static data_unpacker unpacker; +int msg_unpack_start(const void *data, int data_size) +{ + unpacker.reset((const unsigned char *)data, data_size); + return msg_unpack_int(); +} + +int msg_unpack_int() { return unpacker.get_int(); } +const char *msg_unpack_string() { return unpacker.get_string(); } +const unsigned char *msg_unpack_raw(int size) { return unpacker.get_raw(size); } diff --git a/src/engine/network.cpp b/src/engine/network.cpp new file mode 100644 index 000000000..5402aee7d --- /dev/null +++ b/src/engine/network.cpp @@ -0,0 +1,686 @@ +#include + +#include "network.h" +#include "ringbuffer.h" + +/* + header: + unsigned char ID[2]; 2 'T' 'W' + unsigned char version; 3 + unsigned char flags; 4 + unsigned short seq; 6 + unsigned short ack; 8 + unsigned crc; 12 bytes + + header v2: + unsigned char flags; 1 + unsigned char seq_ack[3]; 4 + unsigned char crc[2]; 6 +*/ + + +#define NETWORK_HEADER_V2 + +// move +static int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b) +{ + if( a->ip[0] != b->ip[0] || + a->ip[1] != b->ip[1] || + a->ip[2] != b->ip[2] || + a->ip[3] != b->ip[3] || + a->port != b->port + ) + return 1; + return 0; +} + +enum +{ + NETWORK_VERSION = 1, + +#ifdef NETWORK_HEADER_V2 + NETWORK_HEADER_SIZE = 6, +#else + NETWORK_HEADER_SIZE = 12, +#endif + NETWORK_MAX_PACKET_SIZE = 1024, + NETWORK_MAX_CLIENTS = 16, + + NETWORK_CONNSTATE_OFFLINE=0, + NETWORK_CONNSTATE_CONNECT=1, + NETWORK_CONNSTATE_CONNECTACCEPTED=2, + NETWORK_CONNSTATE_ONLINE=3, + NETWORK_CONNSTATE_ERROR=4, + + NETWORK_PACKETFLAG_CONNECT=0x01, + NETWORK_PACKETFLAG_ACCEPT=0x02, + NETWORK_PACKETFLAG_CLOSE=0x04, + NETWORK_PACKETFLAG_VITAL=0x08, + NETWORK_PACKETFLAG_RESEND=0x10, + //NETWORK_PACKETFLAG_STATELESS=0x20, +}; + +struct NETPACKETDATA +{ + unsigned char ID[2]; + unsigned char version; + unsigned char flags; + unsigned short seq; + unsigned short ack; + unsigned crc; + unsigned data_size; + int64 first_send_time; + unsigned char *data; +}; + + +static void send_packet(NETSOCKET socket, NETADDR4 *addr, NETPACKETDATA *packet) +{ + unsigned char buffer[NETWORK_MAX_PACKET_SIZE]; +#ifdef NETWORK_HEADER_V2 + buffer[0] = packet->flags; + buffer[1] = ((packet->seq>>4)&0xf0) | ((packet->ack>>8)&0x0f); + buffer[2] = packet->seq; + buffer[3] = packet->ack; + buffer[4] = packet->crc>>8; + buffer[5] = packet->crc&0xff; +#else + buffer[0] = packet->ID[0]; + buffer[1] = packet->ID[1]; + buffer[2] = packet->version; + buffer[3] = packet->flags; + buffer[4] = packet->seq>>8; + buffer[5] = packet->seq&0xff; + buffer[6] = packet->ack>>8; + buffer[7] = packet->ack&0xff; + buffer[8] = (packet->crc>>24)&0xff; + buffer[9] = (packet->crc>>16)&0xff; + buffer[10] = (packet->crc>>8)&0xff; + buffer[11] = packet->crc&0xff; +#endif + mem_copy(buffer+NETWORK_HEADER_SIZE, packet->data, packet->data_size); + int send_size = NETWORK_HEADER_SIZE+packet->data_size; + //dbg_msg("network", "sending packet, size=%d (%d + %d)", send_size, NETWORK_HEADER_SIZE, packet->data_size); + net_udp4_send(socket, addr, buffer, send_size); +} + +struct NETCONNECTION +{ + unsigned seq; + unsigned ack; + unsigned state; + + ring_buffer buffer; + + int64 last_recv_time; + int64 last_send_time; + const char *error_string; + + NETADDR4 peeraddr; + NETSOCKET socket; + NETSTATS stats; +}; + +struct NETSLOT +{ + int online; + NETCONNECTION conn; +}; + +struct NETSERVER +{ + NETSOCKET socket; + NETSLOT slots[NETWORK_MAX_CLIENTS]; + unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE]; +}; + +struct NETCLIENT +{ + NETADDR4 server_addr; + NETSOCKET socket; + unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE]; + + NETCONNECTION conn; +}; + +static void conn_reset_stats(NETCONNECTION *conn) +{ + mem_zero(&conn->stats, sizeof(conn->stats)); +} + +static void conn_reset(NETCONNECTION *conn) +{ + conn->seq = 0; + conn->ack = 0; + conn->state = NETWORK_CONNSTATE_OFFLINE; + conn->error_string = 0; + conn->last_send_time = 0; + conn->last_recv_time = 0; + conn->buffer.reset(); +} + +static const char *conn_error(NETCONNECTION *conn) +{ + return conn->error_string; +} +/* +static int conn_state(NETCONNECTION *conn) +{ + return conn->state; +}*/ + +static void conn_init(NETCONNECTION *conn, NETSOCKET socket) +{ + conn_reset(conn); + conn_reset_stats(conn); + conn->socket = socket; +} + +static void conn_ack(NETCONNECTION *conn, int ack) +{ + while(1) + { + ring_buffer::item *i = conn->buffer.first(); + if(!i) + break; + + NETPACKETDATA *resend = (NETPACKETDATA *)i->data(); + if(resend->seq <= ack) + conn->buffer.pop_first(); + else + break; + } +} + +static void conn_send_raw(NETCONNECTION *conn, NETPACKETDATA *data) +{ + conn->last_send_time = time_get(); + conn->stats.send_packets++; + conn->stats.send_bytes += data->data_size + NETWORK_HEADER_SIZE; + send_packet(conn->socket, &conn->peeraddr, data); +} + +static void conn_resend(NETCONNECTION *conn) +{ + ring_buffer::item *i = conn->buffer.first(); + while(i) + { + NETPACKETDATA *resend = (NETPACKETDATA *)i->data(); + conn->stats.resend_packets++; + conn->stats.resend_bytes += resend->data_size + NETWORK_HEADER_SIZE; + conn_send_raw(conn, resend); + i = i->next; + } +} + +static void conn_send(NETCONNECTION *conn, int flags, int data_size, const void *data) +{ + if(flags&NETWORK_PACKETFLAG_VITAL) + conn->seq++; + + NETPACKETDATA p; + p.ID[0] = 'T'; + p.ID[1] = 'W'; + p.version = NETWORK_VERSION; + p.flags = flags; + p.seq = conn->seq; + p.ack = conn->ack; + p.crc = 0; + p.data_size = data_size; + p.data = (unsigned char *)data; + p.first_send_time = time_get(); + + if(flags&NETWORK_PACKETFLAG_VITAL) + { + // save packet if we need to resend + NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.alloc(sizeof(NETPACKETDATA)+p.data_size); + *resend = p; + resend->data = (unsigned char *)(resend+1); + mem_copy(resend->data, p.data, p.data_size); + } + + // TODO: calc crc + conn_send_raw(conn, &p); +} + +static int conn_connect(NETCONNECTION *conn, NETADDR4 *addr) +{ + if(conn->state != NETWORK_CONNSTATE_OFFLINE) + return -1; + + // init connection + conn_reset(conn); + conn->peeraddr = *addr; + conn->state = NETWORK_CONNSTATE_CONNECT; + conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0); + return 0; +} + +static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr) +{ + conn->last_recv_time = time_get(); + conn->stats.recv_packets++; + conn->stats.recv_bytes += p->data_size + NETWORK_HEADER_SIZE; + + if(conn->state == NETWORK_CONNSTATE_OFFLINE) + { + if(p->flags == NETWORK_PACKETFLAG_CONNECT) + { + // send response and init connection + conn->state = NETWORK_CONNSTATE_ONLINE; + conn->peeraddr = *addr; + conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0); + dbg_msg("connection", "got connection, sending connect+accept"); + } + } + else if(net_addr4_cmp(&conn->peeraddr, addr) == 0) + { + if(conn->state == NETWORK_CONNSTATE_ONLINE) + { + // remove packages that are acked + conn_ack(conn, p->ack); + + // + if(p->flags&NETWORK_PACKETFLAG_RESEND) + conn_resend(conn); + + if(p->flags&NETWORK_PACKETFLAG_VITAL) + { + if(p->seq == conn->ack+1) + { + // in sequence + conn->ack++; + } + else + { + // out of sequence, request resend + dbg_msg("conn", "asking for resend"); + conn_send(conn, NETWORK_PACKETFLAG_RESEND, 0, 0); + return 0; + } + } + + return 1; + } + else if(conn->state == NETWORK_CONNSTATE_CONNECT) + { + // connection made + if(p->flags == NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT) + { + conn_send(conn, NETWORK_PACKETFLAG_ACCEPT, 0, 0); + conn->state = NETWORK_CONNSTATE_ONLINE; + dbg_msg("connection", "got connect+accept, sending accept. connection online"); + } + } + /* + else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED) + { + // connection made + if(p->flags == NETWORK_PACKETFLAG_ACCEPT) + { + conn->state = NETWORK_CONNSTATE_ONLINE; + dbg_msg("connection", "got accept. connection online"); + } + }*/ + else + { + // strange packet, wrong state + } + } + else + { + // strange packet, not ment for me + } + + return 0; +} + + + +static void conn_update(NETCONNECTION *conn) +{ + if(conn->state == NETWORK_CONNSTATE_ERROR) + return; + + // check for timeout + if(conn->state != NETWORK_CONNSTATE_OFFLINE && + conn->state != NETWORK_CONNSTATE_CONNECT && + (time_get()-conn->last_recv_time) > time_freq()*3) + { + conn->state = NETWORK_CONNSTATE_ERROR; + conn->error_string = "timeout"; + } + + // check for large buffer errors + if(conn->buffer.size() > 1024*64) + { + conn->state = NETWORK_CONNSTATE_ERROR; + conn->error_string = "too weak connection (out of buffer)"; + } + + if(conn->buffer.first()) + { + NETPACKETDATA *resend = (NETPACKETDATA *)conn->buffer.first()->data(); + if(time_get()-resend->first_send_time > time_freq()*3) + { + conn->state = NETWORK_CONNSTATE_ERROR; + conn->error_string = "too weak connection (not acked for 3 seconds)"; + } + } + + // send keep alives if nothing has happend for 250ms + if(conn->state == NETWORK_CONNSTATE_ONLINE) + { + if(time_get()-conn->last_send_time> time_freq()/4) + conn_send(conn, NETWORK_PACKETFLAG_VITAL, 0, 0); + } + else if(conn->state == NETWORK_CONNSTATE_CONNECT) + { + if(time_get()-conn->last_send_time > time_freq()/2) // send a new connect every 500ms + conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0); + } + else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED) + { + if(time_get()-conn->last_send_time > time_freq()/2) // send a new connect/accept every 500ms + conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0); + } +} + + +static int check_packet(unsigned char *buffer, int size, NETPACKETDATA *packet) +{ + // check the size + if(size < NETWORK_HEADER_SIZE || size > NETWORK_MAX_PACKET_SIZE) + return -1; + + // read the packet +#ifdef NETWORK_HEADER_V2 + packet->ID[0] = 'T'; + packet->ID[1] = 'W'; + packet->version = NETWORK_VERSION; + packet->flags = buffer[0]; + packet->seq = ((buffer[1]&0xf0)<<4)|buffer[2]; + packet->ack = ((buffer[1]&0x0f)<<8)|buffer[3]; + packet->crc = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11]; +#else + packet->ID[0] = buffer[0]; + packet->ID[1] = buffer[1]; + packet->version = buffer[2]; + packet->flags = buffer[3]; + packet->seq = (buffer[4]<<8)|buffer[5]; + packet->ack = (buffer[6]<<8)|buffer[7]; + packet->crc = (buffer[8]<<24)|(buffer[9]<<16)|(buffer[10]<<8)|buffer[11]; +#endif + packet->data_size = size - NETWORK_HEADER_SIZE; + packet->data = buffer+NETWORK_HEADER_SIZE; + + // check the packet + if(packet->ID[0] != 'T' || packet->ID[1] != 'W') + return 1; + + if(packet->version != NETWORK_VERSION) + return 1; + + // TODO: perform crc check + + // return success + return 0; +} + +NETSERVER *net_server_open(int port, int max_clients, int flags) +{ + NETSERVER *server = (NETSERVER *)mem_alloc(sizeof(NETSERVER), 1); + mem_zero(server, sizeof(NETSERVER)); + server->socket = net_udp4_create(port); + + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + conn_init(&server->slots[i].conn, server->socket); + + return server; +} + +int net_server_close(NETSERVER *s) +{ + // TODO: implement me + return 0; +} + +int net_server_newclient(NETSERVER *s) +{ + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + if(!s->slots[i].online && s->slots[i].conn.state == NETWORK_CONNSTATE_ONLINE) + { + s->slots[i].online = 1; + return i; + } + } + + return -1; +} + +int net_server_delclient(NETSERVER *s) +{ + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + if(s->slots[i].online && s->slots[i].conn.state != NETWORK_CONNSTATE_ONLINE) + { + s->slots[i].online = 0; + return i; + } + } + + return -1; +} + +int net_server_drop(NETSERVER *s, int client_id, const char *reason) +{ + // TODO: insert lots of checks here + dbg_msg("net_server", "client dropped. cid=%d reason=\"%s\"", client_id, reason); + conn_reset(&s->slots[client_id].conn); + return 0; +} + +int net_server_update(NETSERVER *s) +{ + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + conn_update(&s->slots[i].conn); + if(conn_error(&s->slots[i].conn)) + net_server_drop(s, i, conn_error(&s->slots[i].conn)); + } + return 0; +} + +int net_server_recv(NETSERVER *s, NETPACKET *packet) +{ + while(1) + { + NETADDR4 addr; + int bytes = net_udp4_recv(s->socket, &addr, s->recv_buffer, NETWORK_MAX_PACKET_SIZE); + + // no more packets for now + if(bytes <= 0) + break; + + NETPACKETDATA data; + int r = check_packet(s->recv_buffer, bytes, &data); + if(r == 0) + { + // ok packet, process it + if(data.flags == NETWORK_PACKETFLAG_CONNECT) + { + // client that wants to connect + int found = 0; + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + if(s->slots[i].conn.state == NETWORK_CONNSTATE_OFFLINE) + { + conn_feed(&s->slots[i].conn, &data, &addr); + found = 1; + break; + } + } + + if(!found) + { + // TODO: send error + } + } + else + { + // find matching slot + for(int i = 0; i < NETWORK_MAX_CLIENTS; i++) + { + if(net_addr4_cmp(&s->slots[i].conn.peeraddr, &addr) == 0) + { + if(conn_feed(&s->slots[i].conn, &data, &addr)) + { + if(data.data_size) + { + packet->client_id = i; + packet->address = addr; + packet->flags = 0; + packet->data_size = data.data_size; + packet->data = data.data; + return 1; + } + } + } + } + } + } + else + { + // errornous packet, drop it + } + + // read header + // do checksum + } + + return 0; +} + +int net_server_send(NETSERVER *s, NETPACKET *packet) +{ + // TODO: insert stuff for stateless stuff + dbg_assert(packet->client_id >= 0, "errornous client id"); + dbg_assert(packet->client_id < NETWORK_MAX_CLIENTS, "errornous client id"); + conn_send(&s->slots[packet->client_id].conn, 0, packet->data_size, packet->data); + return 0; +} + +void net_server_stats(NETSERVER *s, NETSTATS *stats) +{ + mem_zero(stats, sizeof(NETSTATS)); + + int num_stats = sizeof(NETSTATS)/sizeof(int); + int *istats = (int *)stats; + + for(int c = 0; c < NETWORK_MAX_CLIENTS; c++) + { + int *sstats = (int *)(&(s->slots[c].conn.stats)); + for(int i = 0; i < num_stats; i++) + istats[i] += sstats[i]; + } +} + +// +NETCLIENT *net_client_open(int flags) +{ + NETCLIENT *client = (NETCLIENT *)mem_alloc(sizeof(NETCLIENT), 1); + mem_zero(client, sizeof(NETCLIENT)); + client->socket = net_udp4_create(0); + conn_init(&client->conn, client->socket); + return client; +} + +int net_client_close(NETCLIENT *c) +{ + // TODO: implement me + return 0; +} + +int net_client_update(NETCLIENT *c) +{ + // TODO: implement me + conn_update(&c->conn); + if(conn_error(&c->conn)) + net_client_disconnect(c, conn_error(&c->conn)); + return 0; +} + +int net_client_disconnect(NETCLIENT *c, const char *reason) +{ + // TODO: do this more graceful + dbg_msg("net_client", "disconnected. reason=\"%s\"", reason); + conn_reset(&c->conn); + return 0; +} + +int net_client_connect(NETCLIENT *c, NETADDR4 *addr) +{ + //net_client_disconnect(c); + conn_connect(&c->conn, addr); + return 0; +} + +int net_client_recv(NETCLIENT *c, NETPACKET *packet) +{ + while(1) + { + NETADDR4 addr; + int bytes = net_udp4_recv(c->socket, &addr, c->recv_buffer, NETWORK_MAX_PACKET_SIZE); + + // no more packets for now + if(bytes <= 0) + break; + + NETPACKETDATA data; + int r = check_packet(c->recv_buffer, bytes, &data); + if(r == 0) + { + // ok packet, process it + conn_feed(&c->conn, &data, &addr); + // fill in packet + packet->client_id = 0; + packet->address = addr; + packet->flags = 0; + packet->data_size = data.data_size; + packet->data = data.data; + return 1; + } + else + { + // errornous packet, drop it + } + + // read header + // do checksum + } + + return 0; +} + +int net_client_send(NETCLIENT *c, NETPACKET *packet) +{ + // TODO: insert stuff for stateless stuff + dbg_assert(packet->client_id == 0, "errornous client id"); + conn_send(&c->conn, 0, packet->data_size, packet->data); + return 0; +} + +int net_client_state(NETCLIENT *c) +{ + if(c->conn.state == NETWORK_CONNSTATE_ONLINE) + return NETSTATE_ONLINE; + if(c->conn.state == NETWORK_CONNSTATE_OFFLINE) + return NETSTATE_OFFLINE; + return NETSTATE_CONNECTING; +} + +void net_client_stats(NETCLIENT *c, NETSTATS *stats) +{ + *stats = c->conn.stats; +} diff --git a/src/engine/network.h b/src/engine/network.h new file mode 100644 index 000000000..de0358886 --- /dev/null +++ b/src/engine/network.h @@ -0,0 +1,104 @@ + +struct NETPACKET +{ + // -1 means that it's a stateless packet + // 0 on the client means the server + int client_id; + NETADDR4 address; // only used when client_id == -1 + int flags; + int data_size; + const void *data; +}; + +struct NETSTATS +{ + int send_bytes; + int recv_bytes; + int send_packets; + int recv_packets; + + int resend_packets; + int resend_bytes; +}; + +struct NETSERVER; +struct NETCLIENT; + +enum +{ + NETFLAG_ALLOWSTATELESS=1, + PACKETFLAG_VITAL=1, + + NETSTATE_OFFLINE=0, + NETSTATE_CONNECTING, + NETSTATE_ONLINE, +}; + +// server side +NETSERVER *net_server_open(int port, int max_clients, int flags); +int net_server_recv(NETSERVER *s, NETPACKET *packet); +int net_server_send(NETSERVER *s, NETPACKET *packet); +int net_server_close(NETSERVER *s); +int net_server_update(NETSERVER *s); +int net_server_drop(NETSERVER *s, int client_id, const char *reason); +int net_server_newclient(NETSERVER *s); // -1 when no more, else, client id +int net_server_delclient(NETSERVER *s); // -1 when no more, else, client id +void net_server_stats(NETSERVER *s, NETSTATS *stats); + +// client side +NETCLIENT *net_client_open(int flags); +int net_client_disconnect(NETCLIENT *c, const char *reason); +int net_client_connect(NETCLIENT *c, NETADDR4 *addr); +int net_client_recv(NETCLIENT *c, NETPACKET *packet); +int net_client_send(NETCLIENT *c, NETPACKET *packet); +int net_client_close(NETCLIENT *c); +int net_client_update(NETCLIENT *c); +int net_client_state(NETCLIENT *c); +void net_client_stats(NETCLIENT *c, NETSTATS *stats); + + +// wrapper classes for c++ +#ifdef __cplusplus +class net_server +{ + NETSERVER *ptr; +public: + net_server() : ptr(0) {} + ~net_server() { close(); } + + int open(int port, int max, int flags) { ptr = net_server_open(port, max, flags); return ptr != 0; } + int close() { int r = net_server_close(ptr); ptr = 0; return r; } + + int recv(NETPACKET *packet) { return net_server_recv(ptr, packet); } + int send(NETPACKET *packet) { return net_server_send(ptr, packet); } + int update() { return net_server_update(ptr); } + + int drop(int client_id, const char *reason) { return net_server_drop(ptr, client_id, reason); } + int newclient() { return net_server_newclient(ptr); } + int delclient() { return net_server_delclient(ptr); } + + void stats(NETSTATS *stats) { net_server_stats(ptr, stats); } +}; + + +class net_client +{ + NETCLIENT *ptr; +public: + net_client() : ptr(0) {} + ~net_client() { close(); } + + int open(int flags) { ptr = net_client_open(flags); return ptr != 0; } + int close() { int r = net_client_close(ptr); ptr = 0; return r; } + + int connect(NETADDR4 *addr) { return net_client_connect(ptr, addr); } + int disconnect(const char *reason) { return net_client_disconnect(ptr, reason); } + + int recv(NETPACKET *packet) { return net_client_recv(ptr, packet); } + int send(NETPACKET *packet) { return net_client_send(ptr, packet); } + int update() { return net_client_update(ptr); } + + int state() { return net_client_state(ptr); } + void stats(NETSTATS *stats) { net_client_stats(ptr, stats); } +}; +#endif diff --git a/src/engine/packet.h b/src/engine/packet.h index fd93d744e..6c92bf312 100644 --- a/src/engine/packet.h +++ b/src/engine/packet.h @@ -1,437 +1,34 @@ +#include #include #include #include "versions.h" - -#define MACRO_MAKEINT(a,b,c,d) ((a<<24)|(b<<16)|(c<<8)|d) - -// TODO: this is not KISS -class packet -{ - friend class connection; -protected: - enum - { - MAX_PACKET_SIZE = 1024, - }; - - // packet data - struct header - { - unsigned id; - unsigned version; - unsigned size_and_flags; - unsigned crc; - - unsigned msg; - unsigned ack; - unsigned seq; - }; - - unsigned char packet_data[MAX_PACKET_SIZE]; - unsigned char *current; - - // these are used to prepend data in the packet - // used for debugging so we have checks that we - // pack and unpack the same way - enum - { - DEBUG_TYPE_INT=0x1, - DEBUG_TYPE_STR=0x2, - DEBUG_TYPE_RAW=0x3, - }; - - // writes an int to the packet - void write_int_raw(int i) - { - // TODO: check for overflow - *(int*)current = i; - current += sizeof(int); - } - - // reads an int from the packet - int read_int_raw() - { - // TODO: check for overflow - int i = *(int*)current; - current += sizeof(int); - return i; - } - - void debug_insert_mark(int type, int size) - { - write_int_raw((type<<16)|size); - } - - void debug_verify_mark(int type, int size) - { - if(read_int_raw() != ((type<<16) | size)) - dbg_assert(0, "error during packet disassembly"); - } - -public: - - enum - { - FLAG_VITAL=1, - FLAG_RESEND=2 - }; - - packet(unsigned msg=0) - { - current = packet_data; - current += sizeof(header); - - ((header*)packet_data)->id = MACRO_MAKEINT('K','M','A',1); - ((header*)packet_data)->version = TEEWARS_NETVERSION; - ((header*)packet_data)->msg = msg; - } - - void set_header(unsigned ack, unsigned seq) - { - ((header*)packet_data)->ack = ack; - ((header*)packet_data)->seq = seq; - } - - // writes an int to the packet - void write_int(int i) - { - debug_insert_mark(DEBUG_TYPE_INT, 4); - write_int_raw(i); - } - - void write_raw(const char *raw, int size) - { - debug_insert_mark(DEBUG_TYPE_RAW, size); - while(size--) - *current++ = *raw++; - } - - // writes a string to the packet - void write_str(const char *str) - { - debug_insert_mark(DEBUG_TYPE_STR, 0); - int s = strlen(str)+1; - write_int_raw(s); - for(;*str; current++, str++) - *current = *str; - *current = 0; - current++; - } - - // reads an int from the packet - int read_int() - { - debug_verify_mark(DEBUG_TYPE_INT, 4); - return read_int_raw(); - } - - // reads a string from the packet - const char *read_str() - { - debug_verify_mark(DEBUG_TYPE_STR, 0); - int size = read_int_raw(); - const char *s = (const char *)current; - //dbg_msg("packet", "reading string '%s' (%d)", s, size); - current += size; - return s; - } - - const char *read_raw(int size) - { - debug_verify_mark(DEBUG_TYPE_RAW, size); - const char *d = (const char *)current; - current += size; - return d; - } - - // TODO: impelement this - bool is_good() const { return true; } - - unsigned version() const { return ((header*)packet_data)->version; } - unsigned msg() const { return ((header*)packet_data)->msg; } - unsigned seq() const { return ((header*)packet_data)->seq; } - unsigned ack() const { return ((header*)packet_data)->ack; } - unsigned flags() const { return (((header*)packet_data)->size_and_flags) & 0xffff; } - - // access functions to get the size and data - int size() const { return (int)(current-(unsigned char*)packet_data); } - int max_size() const { return MAX_PACKET_SIZE; } - void *data() { return packet_data; } -}; - -// TODO: remove all the allocations from this class -class ring_buffer -{ - struct item - { - item *next; - item *prev; - int size; - }; - - item *first; - item *last; - - unsigned buffer_size; -public: - ring_buffer() - { - first = 0; - last = 0; - buffer_size = 0; - } - - ~ring_buffer() - { - reset(); - } - - void reset() - { - // clear all - while(peek_data()) - next(); - } - - void *alloc(int size) - { - item *i = (item*)mem_alloc(sizeof(item)+size, 1); - i->size = size; - - i->prev = last; - i->next = 0; - if(last) - last->next = i; - else - first = i; - last = i; - - buffer_size += size; - return (void*)(i+1); - } - - unsigned peek_size() - { - if(!first) - return 0; - return first->size; - } - - void *peek_data() - { - if(!first) - return 0; - return (void*)(first+1); - } - - void next() - { - if(first) - { - item *next = first->next; - buffer_size += first->size; - mem_free(first); - first = next; - if(first) - first->prev = 0; - else - last = 0; - } - } - - unsigned size() { return buffer_size; } -}; - -// -class connection -{ - baselib::socket_udp4 *socket; - baselib::netaddr4 addr; - unsigned seq; - unsigned ack; - - unsigned counter_sent_bytes; - unsigned counter_recv_bytes; - - int needs_resend; - - /* - struct resend_packet - { - resend_packet *next; - unsigned seq; - unsigned msg; - unsigned size; - char data[1]; - }; - - resend_packet *first_resend; - resend_packet *last_resend; - */ - - ring_buffer resend_buffer; - - void save_for_resend(packet *p) - { - /* - packet *n = (packet *)resend_buffer.alloc(p->size()); - mem_copy(n->data(), p->data(), p->size()); - n->current = (unsigned char*)n->data() + p->size(); - */ - } - - void remove_resends(unsigned ack) - { - /* - while(1) - { - packet *p = (packet *)resend_buffer.peek_data(); - if(!p) - break; - - if(p->seq() > ack) - break; - resend_buffer.next(); - }*/ - } - -public: - void counter_reset() - { - counter_sent_bytes = 0; - counter_recv_bytes = 0; - } - - void counter_get(unsigned *sent, unsigned *recved) - { - *sent = counter_sent_bytes; - *recved = counter_recv_bytes; - } - - void init(baselib::socket_udp4 *socket, const baselib::netaddr4 *addr) - { - resend_buffer.reset(); - - this->addr = *addr; - this->socket = socket; - ack = 0; - seq = 0; - needs_resend = 0; - counter_reset(); - } - - void send(packet *p) - { - if(p->flags()&packet::FLAG_VITAL) - seq++; - - p->set_header(ack, seq); - - if(p->flags()&packet::FLAG_VITAL) - save_for_resend(p); - - // TODO: request resend if needed, use needs_resend variable - - socket->send(&address(), p->data(), p->size()); - counter_sent_bytes += p->size(); - } - - packet *feed(packet *p) - { - counter_recv_bytes += p->size(); - - if(p->flags()&packet::FLAG_VITAL) - { - if(p->seq() == ack+1) - { - // packet in seqence, ack it - ack++; - //dbg_msg("network/connection", "packet in sequence. seq/ack=%x", ack); - return p; - } - else if(p->seq() > ack) - { - // packet loss - needs_resend = 1; - dbg_msg("network/connection", "packet loss! seq=%x ack=%x+1", p->seq(), ack); - return p; - } - else - { - // we already got this packet - return 0x0; - } - } - - // remove resends - remove_resends(p->ack()); - - // handle resends - if(p->flags()&packet::FLAG_RESEND) - { - // peer as requested a resend of all non acked packages. - - } - - return p; - } - - const baselib::netaddr4 &address() const { return addr; } - - void update() - { - } -}; - -//const char *NETWORK_VERSION = "development"; +#include "ringbuffer.h" +#include "compression.h" +#include "snapshot.h" enum { - NETMSG_CONTEXT_CONNECT=0x00010000, - NETMSG_CONTEXT_GAME=0x00020000, - NETMSG_CONTEXT_GLOBAL=0x00040000, + NETMSG_NULL=0, - // connection phase - NETMSG_CLIENT_CONNECT=NETMSG_CONTEXT_CONNECT|1, - // str32 name - // str32 clan - // str32 password - // str32 skin + // sent by server + NETMSG_MAP, + NETMSG_SNAP, + NETMSG_SNAPEMPTY, + NETMSG_SNAPSMALL, - // TODO: These should be implemented to make the server send the map to the client on connect - // NETMSG_CLIENT_FETCH, - // NETMSG_SERVER_MAPDATA, + // sent by client + NETMSG_INFO, + NETMSG_ENTERGAME, + NETMSG_INPUT, + NETMSG_SNAPACK, - NETMSG_SERVER_ACCEPT=NETMSG_CONTEXT_CONNECT|2, - // str32 mapname - - - NETMSG_CLIENT_DONE=NETMSG_CONTEXT_CONNECT|3, - // nothing - - // game phase - NETMSG_SERVER_SNAP = NETMSG_CONTEXT_GAME|1, // server will spam these - // int num_parts - // int part - // int size - // data * - - NETMSG_CLIENT_INPUT = NETMSG_CONTEXT_GAME|1, // client will spam these - // int input[MAX_INPUTS] - - NETMSG_SERVER_EVENT = NETMSG_CONTEXT_GAME|2, - NETMSG_CLIENT_EVENT = NETMSG_CONTEXT_GAME|2, - - NETMSG_CLIENT_CHECKALIVE = NETMSG_CONTEXT_GAME|3, // check if client is alive - - NETMSG_CLIENT_ERROR=0x0fffffff, - // str128 reason - - NETMSG_SERVER_ERROR=0x0fffffff, - // str128 reason + // sent by both + NETMSG_ERROR, }; + +// this should be revised enum { MAX_NAME_LENGTH=32, @@ -440,3 +37,320 @@ enum MAX_SNAPSHOT_SIZE=64*1024, MAX_SNAPSHOT_PACKSIZE=768 }; + + +class snapshot_storage +{ + struct holder + { + int tick; + int data_size; + int *data() { return (int *)(this+1); } + }; + + ring_buffer buffer; + +public: + void purge_until(int tick) + { + while(1) + { + ring_buffer::item *i = buffer.first(); + if(!i) + break; + holder *h = (holder *)i->data(); + if(h->tick < tick) + buffer.pop_first(); + else + break; + } + } + + void purge_all() + { + buffer.reset(); + } + + void add(int tick, int data_size, void *data) + { + holder *h = (holder *)buffer.alloc(sizeof(holder)+data_size); + h->tick = tick; + h->data_size = data_size; + mem_copy(h->data(), data, data_size); + } + + int get(int tick, void **data) + { + ring_buffer::item *i = buffer.first(); + while(i) + { + holder *h = (holder *)i->data(); + if(h->tick == tick) + { + *data = h->data(); + return h->data_size; + } + + i = i->next; + } + + return -1; + } +}; +/* +class snapshot_delta_builder +{ +public: + static const int MAX_ITEMS = 512; + + char data[MAX_SNAPSHOT_SIZE]; + int data_size; + + int offsets[MAX_ITEMS]; + int num_items; + + int top_size; + int top_items; + + int snapnum; + + snapshot_delta_builder() + { + top_size = 0; + top_items = 0; + snapnum = 0; + } + + void start() + { + data_size = 0; + num_items = 0; + } + + int finish(void *snapdata) + { + snapnum++; + + // flattern and make the snapshot + snapshot *snap = (snapshot *)snapdata; + snap->data_size = data_size; + snap->num_items = num_items; + int offset_size = sizeof(int)*num_items; + mem_copy(snap->offsets, offsets, offset_size); + mem_copy(snap->data_start(), data, data_size); + return sizeof(int) + offset_size + data_size; + } + + void *new_item(int type, int id, int size) + { + snapshot::item *obj = (snapshot::item *)(data+data_size); + obj->type_and_id = (type<<16)|id; + offsets[num_items] = data_size; + data_size += sizeof(int) + size; + num_items++; + dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data"); + dbg_assert(num_items < MAX_ITEMS, "too many items"); + + return &obj->data; + } +}; +*/ + +class snapshot_builder +{ +public: + static const int MAX_ITEMS = 512; + + char data[MAX_SNAPSHOT_SIZE]; + int data_size; + + int offsets[MAX_ITEMS]; + int num_items; + + int top_size; + int top_items; + + int snapnum; + + snapshot_builder() + { + top_size = 0; + top_items = 0; + snapnum = 0; + } + + void start() + { + data_size = 0; + num_items = 0; + } + + snapshot::item *get_item(int index) + { + return (snapshot::item *)&(data[offsets[index]]); + } + + int *get_item_data(int key) + { + for(int i = 0; i < num_items; i++) + { + if(get_item(i)->key() == key) + return (int *)get_item(i)->data(); + } + return 0; + } + + int finish(void *snapdata) + { + snapnum++; + + // flattern and make the snapshot + snapshot *snap = (snapshot *)snapdata; + snap->data_size = data_size; + snap->num_items = num_items; + int offset_size = sizeof(int)*num_items; + mem_copy(snap->offsets(), offsets, offset_size); + mem_copy(snap->data_start(), data, data_size); + return sizeof(snapshot) + offset_size + data_size; + } + + void *new_item(int type, int id, int size) + { + snapshot::item *obj = (snapshot::item *)(data+data_size); + obj->type_and_id = (type<<16)|id; + offsets[num_items] = data_size; + data_size += sizeof(snapshot::item) + size; + num_items++; + dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data"); + dbg_assert(num_items < MAX_ITEMS, "too many items"); + + return obj->data(); + } +}; + +class data_packer +{ + enum + { + BUFFER_SIZE=1024*2 + }; + + unsigned char buffer[BUFFER_SIZE]; + unsigned char *current; + unsigned char *end; + int error; +public: + void reset() + { + error = 0; + current = buffer; + end = current + BUFFER_SIZE; + } + + void add_int(int i) + { + // TODO: add space check + // TODO: variable length encoding perhaps + // TODO: add debug marker + current = vint_pack(current, i); + //*current++ = (i>>24)&0xff; + //*current++ = (i>>16)&0xff; + //*current++ = (i>>8)&0xff; + //*current++ = i&0xff; + } + + void add_string(const char *p, int limit) + { + // TODO: add space check + // TODO: add debug marker + if(limit > 0) + { + while(*p && limit != 0) + { + *current++ = *p++; + limit--; + } + *current++ = 0; + } + else + { + while(*p) + *current++ = *p++; + *current++ = 0; + } + } + + void add_raw(const unsigned char *data, int size) + { + // TODO: add space check + // TODO: add debug marker + //add_int(size); + while(size) + { + *current++ = *data++; + size--; + } + } + + int size() const + { + return (const unsigned char *)current-(const unsigned char *)buffer; + } + + const unsigned char *data() + { + return (const unsigned char *)buffer; + } +}; + +class data_unpacker +{ + const unsigned char *current; + const unsigned char *start; + const unsigned char *end; + int error; + +public: + void reset(const unsigned char *data, int size) + { + error = 0; + start = data; + end = start + size; + current = start; + } + + int get_int() + { + int i; + current = vint_unpack(current, &i); + // TODO: might be changed into variable width + // TODO: add range check + // TODO: add debug marker + //i = (current[0]<<24) | (current[1]<<16) | (current[2]<<8) | (current[3]); + //current += 4; + return i; + } + + const char *get_string() + { + // TODO: add range check + // TODO: add debug marker + const char *ptr = (const char *)current; + while(*current) // skip the string + current++; + current++; + return ptr; + } + + const unsigned char *get_raw(int size) + { + // TODO: add range check + // TODO: add debug marker + //int s = get_int(); + //if(size) + //*size = s; + const unsigned char *ptr = current; + current += size; + return ptr; + } +}; diff --git a/src/engine/ringbuffer.h b/src/engine/ringbuffer.h new file mode 100644 index 000000000..3208efbf2 --- /dev/null +++ b/src/engine/ringbuffer.h @@ -0,0 +1,84 @@ + +// TODO: remove all the allocations from this class +class ring_buffer +{ +public: + struct item + { + item *next; + item *prev; + int size; + unsigned char *data() { return (unsigned char *)(this+1); } + }; + + item *first_item; + item *last_item; + + unsigned buffer_size; + + ring_buffer() + { + first_item = 0; + last_item = 0; + buffer_size = 0; + } + + ~ring_buffer() + { + reset(); + } + + void reset() + { + // clear all + while(first()) + pop_first(); + } + + void *alloc(int size) + { + item *i = (item*)mem_alloc(sizeof(item)+size, 1); + i->size = size; + + i->prev = last_item; + i->next = 0; + if(last_item) + last_item->next = i; + else + first_item = i; + last_item = i; + + buffer_size += size; + return i->data(); + } + + item *first() + { + return first_item; + } + + /* + void *peek_data() + { + if(!first) + return 0; + return (void*)(first+1); + }*/ + + void pop_first() + { + if(first_item) + { + item *next = first_item->next; + buffer_size -= first_item->size; + mem_free(first_item); + first_item = next; + if(first_item) + first_item->prev = 0; + else + last_item = 0; + } + } + + unsigned size() { return buffer_size; } +}; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index d0ae80f0f..5632689e3 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -1,139 +1,24 @@ -#include -#include +#include +#include +#include #include #include -//#include "socket.h" #include #include -#include +#include #include +#include +#include + + namespace baselib {} using namespace baselib; -int net_addr4_cmp(const NETADDR4 *a, const NETADDR4 *b) -{ - if( - a->ip[0] != b->ip[0] || - a->ip[1] != b->ip[1] || - a->ip[2] != b->ip[2] || - a->ip[3] != b->ip[3] || - a->port != b->port - ) - return 1; - return 0; -} - -// --- string handling (MOVE THESE!!) --- -void snap_encode_string(const char *src, int *dst, int length, int max_length) -{ - const unsigned char *p = (const unsigned char *)src; - - // handle whole int - for(int i = 0; i < length/4; i++) - { - *dst = (p[0]<<24|p[1]<<16|p[2]<<8|p[3]); - p += 4; - dst++; - } - - // take care of the left overs - int left = length%4; - if(left) - { - unsigned last = 0; - switch(left) - { - case 3: last |= p[2]<<8; - case 2: last |= p[1]<<16; - case 1: last |= p[0]<<24; - } - *dst = last; - } -} - - -class snapshot_builder -{ -public: - static const int MAX_ITEMS = 512; - //static const int MAX_DATA_SIZE=1*1024; - - char data[MAX_SNAPSHOT_SIZE]; - int data_size; - - int offsets[MAX_ITEMS]; - int num_items; - - int top_size; - int top_items; - - int snapnum; - - snapshot_builder() - { - top_size = 0; - top_items = 0; - snapnum = 0; - } - - void start() - { - data_size = 0; - num_items = 0; - } - - int finish(void *snapdata) - { - snapnum++; - - // collect some data - /* - int change = 0; - if(data_size > top_size) - { - change++; - top_size = data_size; - } - - if(num_items > top_items) - { - change++; - top_items = num_items; - } - - if(change) - { - dbg_msg("snapshot", "new top, items=%d size=%d", top_items, top_size); - }*/ - - // flattern and make the snapshot - snapshot *snap = (snapshot *)snapdata; - snap->num_items = num_items; - int offset_size = sizeof(int)*num_items; - mem_copy(snap->offsets, offsets, offset_size); - mem_copy(snap->data_start(), data, data_size); - return sizeof(int) + offset_size + data_size; - } - - void *new_item(int type, int id, int size) - { - snapshot::item *obj = (snapshot::item *)(data+data_size); - obj->type_and_id = (type<<16)|id; - offsets[num_items] = data_size; - data_size += sizeof(int) + size; - num_items++; - dbg_assert(data_size < MAX_SNAPSHOT_SIZE, "too much data"); - dbg_assert(num_items < MAX_ITEMS, "too many items"); - - return &obj->data; - } -}; - static snapshot_builder builder; void *snap_new_item(int type, int id, int size) @@ -143,7 +28,6 @@ void *snap_new_item(int type, int id, int size) return builder.new_item(type, id, size); } - // class client { @@ -157,34 +41,21 @@ public: // connection state info int state; - - // (ticks) if lastactivity > 5 seconds kick him - int64 lastactivity; - connection conn; + + int last_acked_snapshot; + snapshot_storage snapshots; char name[MAX_NAME_LENGTH]; char clan[MAX_CLANNAME_LENGTH]; - /* - client() - { - state = STATE_EMPTY; - name[0] = 0; - clan[0] = 0; - } - - ~client() - { - dbg_assert(state == STATE_EMPTY, "client destoyed while in use"); - }*/ bool is_empty() const { return state == STATE_EMPTY; } bool is_ingame() const { return state == STATE_INGAME; } - const netaddr4 &address() const { return conn.address(); } }; static client clients[MAX_CLIENTS]; static int current_tick = 0; static int send_heartbeats = 1; +static net_server net; int server_tick() { @@ -193,7 +64,7 @@ int server_tick() int server_tickspeed() { - return 50; + return SERVER_TICK_SPEED; } int server_init() @@ -203,7 +74,7 @@ int server_init() clients[i].state = client::STATE_EMPTY; clients[i].name[0] = 0; clients[i].clan[0] = 0; - clients[i].lastactivity = 0; + //clients[i].lastactivity = 0; } current_tick = 0; @@ -225,12 +96,27 @@ int server_getclientinfo(int client_id, client_info *info) return 0; } -// + +int server_send_msg(int client_id) +{ + const msg_info *info = msg_get_info(); + NETPACKET packet; + packet.client_id = client_id; + packet.data = info->data; + packet.data_size = info->size; + + if(info->flags&MSGFLAG_VITAL) + packet.flags = PACKETFLAG_VITAL; + + net.send(&packet); + return 0; +} + +// TODO: remove this class class server { public: - - socket_udp4 game_socket; + //socket_udp4 game_socket; const char *map_name; const char *server_name; @@ -256,14 +142,14 @@ public: } // start server - if(!game_socket.open(8303)) + if(!net.open(8303, 0, 0)) { dbg_msg("network/server", "couldn't open socket"); return false; } - for(int i = 0; i < MAX_CLIENTS; i++) - dbg_msg("network/server", "\t%d: %d", i, clients[i].state); + //for(int i = 0; i < MAX_CLIENTS; i++) + //dbg_msg("network/server", "\t%d: %d", i, clients[i].state); if (net_host_lookup(MASTER_SERVER_ADDRESS, MASTER_SERVER_PORT, &master_server) != 0) { @@ -304,17 +190,6 @@ public: snaptime += time_get()-start; } - // Check for client timeouts - for (int i = 0; i < MAX_CLIENTS; i++) - { - if (clients[i].state != client::STATE_EMPTY) - { - // check last activity time - if (((lasttick - clients[i].lastactivity) / time_freq()) > SERVER_CLIENT_TIMEOUT) - client_timeout(i); - } - } - lasttick += time_per_tick; } @@ -332,8 +207,7 @@ public: // TODO: fix me netaddr4 me(127, 0, 0, 0, 8303); - - send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname); + //send_heartbeat(0, &me, players, MAX_CLIENTS, server_name, mapname); } lastheartbeat = t+time_per_heartbeat; @@ -357,6 +231,7 @@ public: (simulationtime+snaptime+networktime)/(float)reportinterval*100.0f); unsigned sent_total=0, recv_total=0; + /* for (int i = 0; i < MAX_CLIENTS; i++) if (!clients[i].is_empty()) { @@ -366,7 +241,7 @@ public: sent_total += s; recv_total += r; } - + */ dbg_msg("server/report", "biggestsnap=%d send=%d recv=%d", biggest_snapshot, sent_total/3, recv_total/3); @@ -393,6 +268,8 @@ public: void snap() { + //if(current_tick&1) + // return; mods_presnap(); for(int i = 0; i < MAX_CLIENTS; i++) @@ -400,33 +277,86 @@ public: if(clients[i].is_ingame()) { char data[MAX_SNAPSHOT_SIZE]; + char deltadata[MAX_SNAPSHOT_SIZE]; char compdata[MAX_SNAPSHOT_SIZE]; + //char intdata[MAX_SNAPSHOT_SIZE]; builder.start(); mods_snap(i); // finish snapshot int snapshot_size = builder.finish(data); - // compress it - int compsize = lzw_compress(data, snapshot_size, compdata); - snapshot_size = compsize; - - if(snapshot_size > biggest_snapshot) - biggest_snapshot = snapshot_size; - - const int max_size = MAX_SNAPSHOT_PACKSIZE; - int numpackets = (snapshot_size+max_size-1)/max_size; - for(int n = 0, left = snapshot_size; left; n++) + // remove old snapshos + // keep 1 seconds worth of snapshots + clients[i].snapshots.purge_until(current_tick-SERVER_TICK_SPEED); + + // save it the snapshot + clients[i].snapshots.add(current_tick, snapshot_size, data); + + // find snapshot that we can preform delta against + static snapshot emptysnap; + emptysnap.data_size = 0; + emptysnap.num_items = 0; + + snapshot *deltashot = &emptysnap; + int deltashot_size; + int delta_tick = -1; { - int chunk = left < max_size ? left : max_size; - left -= chunk; + void *delta_data; + deltashot_size = clients[i].snapshots.get(clients[i].last_acked_snapshot, (void **)&delta_data); + if(deltashot_size >= 0) + { + delta_tick = clients[i].last_acked_snapshot; + deltashot = (snapshot *)delta_data; + } + } + + // create delta + int deltasize = snapshot_create_delta(deltashot, (snapshot*)data, deltadata); + + if(deltasize) + { + // compress it + //int intsize = -1; + unsigned char intdata[MAX_SNAPSHOT_SIZE]; + int intsize = intpack_compress(deltadata, deltasize, intdata); + + int compsize = zerobit_compress(intdata, intsize, compdata); + //dbg_msg("compress", "%5d --delta-> %5d --int-> %5d --zero-> %5d %5d", + //snapshot_size, deltasize, intsize, compsize, intsize-compsize); + snapshot_size = compsize; - packet p(NETMSG_SERVER_SNAP); - p.write_int(numpackets); - p.write_int(n); - p.write_int(chunk); - p.write_raw(&compdata[n*max_size], chunk); - clients[i].conn.send(&p); + if(snapshot_size > biggest_snapshot) + biggest_snapshot = snapshot_size; + + const int max_size = MAX_SNAPSHOT_PACKSIZE; + int numpackets = (snapshot_size+max_size-1)/max_size; + for(int n = 0, left = snapshot_size; left; n++) + { + int chunk = left < max_size ? left : max_size; + left -= chunk; + + if(numpackets == 1) + msg_pack_start(NETMSG_SNAPSMALL, 0); + else + msg_pack_start(NETMSG_SNAP, 0); + msg_pack_int(current_tick); + msg_pack_int(current_tick-delta_tick); // compressed with + msg_pack_int(chunk); + msg_pack_raw(&compdata[n*max_size], chunk); + msg_pack_end(); + //const msg_info *info = msg_get_info(); + //dbg_msg("server", "size=%d", info->size); + server_send_msg(i); + } + } + else + { + msg_pack_start(NETMSG_SNAPEMPTY, 0); + msg_pack_int(current_tick); + msg_pack_int(current_tick-delta_tick); // compressed with + msg_pack_end(); + server_send_msg(i); } } } @@ -434,11 +364,12 @@ public: mods_postsnap(); } - void send_accept(client *client, const char *map) + void send_map(int cid) { - packet p(NETMSG_SERVER_ACCEPT); - p.write_str(map); - client->conn.send(&p); + msg_pack_start(NETMSG_MAP, MSGFLAG_VITAL); + msg_pack_string(map_name, 0); + msg_pack_end(); + server_send_msg(cid); } void drop(int cid, const char *reason) @@ -451,161 +382,47 @@ public: dbg_msg("game", "player dropped. reason='%s' cid=%x name='%s'", reason, cid, clients[cid].name); } - int find_client(const netaddr4 *addr) + void process_client_packet(NETPACKET *packet) { - // fetch client - for(int i = 0; i < MAX_CLIENTS; i++) + int cid = packet->client_id; + int msg = msg_unpack_start(packet->data, packet->data_size); + if(msg == NETMSG_INFO) { - if(!clients[i].is_empty() && clients[i].address() == *addr) - return i; + strncpy(clients[cid].name, msg_unpack_string(), MAX_NAME_LENGTH); + strncpy(clients[cid].clan, msg_unpack_string(), MAX_CLANNAME_LENGTH); + const char *password = msg_unpack_string(); + const char *skin = msg_unpack_string(); + (void)password; // ignore these variables + (void)skin; + send_map(cid); } - return -1; - } - - void client_process_packet(int cid, packet *p) - { - clients[cid].lastactivity = lasttick; - if(p->msg() == NETMSG_CLIENT_DONE) + else if(msg == NETMSG_ENTERGAME) { dbg_msg("game", "player as entered the game. cid=%x", cid); clients[cid].state = client::STATE_INGAME; mods_client_enter(cid); } - else if(p->msg() == NETMSG_CLIENT_INPUT) + else if(msg == NETMSG_INPUT) { int input[MAX_INPUT_SIZE]; - int size = p->read_int(); + int size = msg_unpack_int(); for(int i = 0; i < size/4; i++) - input[i] = p->read_int(); - if(p->is_good()) - { - //dbg_msg("network/server", "applying input %d %d %d", input[0], input[1], input[2]); - mods_client_input(cid, input); - } + input[i] = msg_unpack_int(); + mods_client_input(cid, input); } - else if(p->msg() == NETMSG_CLIENT_ERROR) + else if(msg == NETMSG_SNAPACK) { - const char *reason = p->read_str(); - if(p->is_good()) - dbg_msg("network/server", "client error. cid=%x reason='%s'", cid, reason); - else - dbg_msg("network/server", "client error. cid=%x", cid); - drop(cid, "client error"); + clients[cid].last_acked_snapshot = msg_unpack_int(); } else { - dbg_msg("network/server", "invalid message. cid=%x msg=%x", cid, p->msg()); - drop(cid, "invalid message"); + dbg_msg("server", "strange message cid=%d msg=%d data_size=%d", cid, msg, packet->data_size); } + } - void process_packet(packet *p, netaddr4 *from) + void process_packet(NETPACKET *packet) { - // do version check - if(p->version() != TEEWARS_NETVERSION) - { - // send an empty packet back. - // this will allow the client to check the version - packet p; - game_socket.send(from, p.data(), p.size()); - return; - } - - if(p->msg() == NETMSG_CLIENT_CONNECT) - { - // we got no state for this client yet - const char *version; - const char *name; - const char *clan; - const char *password; - const char *skin; - - version = p->read_str(); - name = p->read_str(); - clan = p->read_str(); - password = p->read_str(); - skin = p->read_str(); - - if(p->is_good()) - { - /* - // check version - if(strcmp(version, TEEWARS_NETVERSION) != 0) - { - dbg_msg("network/server", "wrong version connecting '%s'", version); - // TODO: send error - return; - }*/ - - // look for empty slot, linear search - int id = -1; - for(int i = 0; i < MAX_CLIENTS; i++) - if(clients[i].is_empty()) - { - id = i; - break; - } - - if(id != -1) - { - // slot found - // TODO: perform correct copy here - mem_copy(clients[id].name, name, MAX_NAME_LENGTH); - mem_copy(clients[id].clan, clan, MAX_CLANNAME_LENGTH); - clients[id].state = client::STATE_CONNECTING; - clients[id].conn.init(&game_socket, from); - - clients[id].lastactivity = lasttick; - clients[id].name[MAX_NAME_LENGTH-1] = 0; - clients[id].clan[MAX_CLANNAME_LENGTH-1] = 0; - - dbg_msg("network/server", "client connected. '%s' on slot %d", name, id); - - // TODO: return success - send_accept(&clients[id], map_name); - } - else - { - // no slot found - // TODO: send error - dbg_msg("network/server", "client connected but server is full"); - - for(int i = 0; i < MAX_CLIENTS; i++) - dbg_msg("network/server", "\t%d: %d", i, clients[i].state); - } - } - } - else - { - int cid = find_client(from); - if(cid >= 0) - { - if(clients[cid].conn.feed(p)) - { - // packet is ok - unsigned msg = p->msg(); - - // client found, check state - if(((msg>>16)&0xff)&clients[cid].state) - { - // state is ok - client_process_packet(cid, p); - } - else - { - // invalid state, disconnect the client - drop(cid, "invalid message at this state"); - } - } - else - { - drop(cid, "connection error"); - } - - } - else - dbg_msg("network/server", "packet from strange address."); - } } void client_timeout(int clientId) @@ -615,67 +432,53 @@ public: void pump_network() { + net.update(); + + // process packets + NETPACKET packet; + while(net.recv(&packet)) + { + + if(packet.client_id == -1) + { + // stateless + } + else + process_client_packet(&packet); + } + + // check for removed clients while(1) { - packet p; - netaddr4 from; - - //int bytes = net_udp4_recv( - int bytes = game_socket.recv(&from, p.data(), p.max_size()); - //int bytes = game_socket.recv(&from, p.data(), p.max_size()); - if(bytes <= 0) + int cid = net.delclient(); + if(cid == -1) break; - - process_packet(&p, &from); + + clients[cid].state = client::STATE_EMPTY; + clients[cid].name[0] = 0; + clients[cid].clan[0] = 0; + clients[cid].snapshots.purge_all(); + + mods_client_drop(cid); + + dbg_msg("server", "del client %d", cid); + } + + // check for new clients + while(1) + { + int cid = net.newclient(); + if(cid == -1) + break; + + clients[cid].state = client::STATE_CONNECTING; + clients[cid].name[0] = 0; + clients[cid].clan[0] = 0; + clients[cid].snapshots.purge_all(); + clients[cid].last_acked_snapshot = -1; + + dbg_msg("server", "new client %d", cid); } - // TODO: check for client timeouts - } - - char *write_int(char *buffer, int integer) - { - *buffer++ = integer >> 24; - *buffer++ = integer >> 16; - *buffer++ = integer >> 8; - *buffer++ = integer; - - return buffer; - } - - char *write_netaddr4(char *buffer, NETADDR4 *address) - { - *buffer++ = address->ip[0]; - *buffer++ = address->ip[1]; - *buffer++ = address->ip[2]; - *buffer++ = address->ip[3]; - - return write_int(buffer, address->port); - } - - void send_heartbeat(int version, netaddr4 *address, int players, int max_players, const char *name, const char *map_name) - { - char buffer[216] = {0}; - char *d = buffer; - - d = write_int(d, 'TWHB'); - d = write_int(d, version); - d = write_netaddr4(d, address); - d = write_int(d,players); - d = write_int(d, max_players); - - int len = strlen(name); - if (len > 128) - len = 128; - - memcpy(d, name, len); - d += 128; - - len = strlen(map_name); - if (len > 64) - len = 64; - - memcpy(d, map_name, len); - d += 64; - game_socket.send(&master_server, buffer, sizeof(buffer)); } }; @@ -683,8 +486,14 @@ int main(int argc, char **argv) { dbg_msg("server", "starting..."); + dbg_msg("server", "%d %d", sizeof(snapshot), sizeof(snapshot::item)); + + config_reset(); + config_load("server.cfg"); + const char *mapname = "data/demo.map"; const char *servername = 0; + // parse arguments for(int i = 1; i < argc; i++) { @@ -705,6 +514,12 @@ int main(int argc, char **argv) // -p (private server) send_heartbeats = 0; } + else if(argv[i][0] == '-' && argv[i][1] == 'o' && argv[i][2] == 0) + { + // -o port + i++; + config_set_sv_port(&config, atol(argv[i])); + } } if(!mapname) diff --git a/src/engine/snapshot.cpp b/src/engine/snapshot.cpp new file mode 100644 index 000000000..2f6f1f36e --- /dev/null +++ b/src/engine/snapshot.cpp @@ -0,0 +1,235 @@ + +#include "packet.h" +#include "snapshot.h" + +struct snapshot_delta +{ + int num_deleted_items; + int num_update_items; + int num_temp_items; // needed? + int data[1]; + + /* + char *data_start() { return (char *)&offsets[num_deleted_items+num_update_items+num_temp_items]; } + + int deleted_item(int index) { return offsets[index]; } + item *update_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+index]); } + item *temp_item(int index) { return (item *)(data_start() + offsets[num_deleted_items+num_update_items+index]); } + * */ +}; + + +static const int MAX_ITEMS = 512; +static snapshot_delta empty = {0,0,0,{0}}; + +void *snapshot_empty_delta() +{ + return ∅ +} + +static int diff_item(int *past, int *current, int *out, int size) +{ + /* + int needed = 0; + while(size) + { + *out = *current-*past; + if(*out) + needed = 1; + out++; + current++; + past++; + size--; + }*/ + + int needed = 0; + while(size) + { + *out = *current-*past; + if(*out) + needed = 1; + + out++; + past++; + current++; + size--; + } + + return needed; +} + +// 1 = 4-3 +// d = n-p + +// n(4) = p(3)+d(1) + +static void undiff_item(int *past, int *diff, int *out, int size) +{ + while(size) + { + *out = *past+*diff; + out++; + past++; + diff++; + size--; + } +} + +int snapshot_create_delta(snapshot *from, snapshot *to, void *dstdata) +{ + //int deleted[MAX_ITEMS]; + //int update[MAX_ITEMS]; + //int mark[MAX_ITEMS]; + //char data[MAX_SNAPSHOT_SIZE]; + + snapshot_delta *delta = (snapshot_delta *)dstdata; + int *data = (int *)delta->data; + + + delta->num_deleted_items = 0; + delta->num_update_items = 0; + delta->num_temp_items = 0; + + // pack deleted stuff + for(int i = 0; i < from->num_items; i++) + { + snapshot::item *fromitem = from->get_item(i); + if(to->get_item_index(fromitem->key()) == -1) + { + // deleted + delta->num_deleted_items++; + *data = fromitem->key(); + data++; + } + } + + // pack updated stuff + int count = 0, size_count = 0; + for(int i = 0; i < to->num_items; i++) + { + // do delta + int itemsize = to->get_item_datasize(i); + + snapshot::item *curitem = to->get_item(i); + int pastindex = from->get_item_index(curitem->key()); + if(pastindex != -1) + { + snapshot::item *pastitem = from->get_item(pastindex); + if(diff_item((int*)pastitem->data(), (int*)curitem->data(), data+3, itemsize/4)) + { + *data++ = itemsize; + *data++ = curitem->type(); + *data++ = curitem->id(); + //*data++ = curitem->key(); + data += itemsize/4; + delta->num_update_items++; + } + } + else + { + *data++ = itemsize; + *data++ = curitem->type(); + *data++ = curitem->id(); + //*data++ = curitem->key(); + + mem_copy(data, curitem->data(), itemsize); + size_count += itemsize; + data += itemsize/4; + delta->num_update_items++; + count++; + } + } + + if(0) + { + dbg_msg("snapshot", "%d %d %d", + delta->num_deleted_items, + delta->num_update_items, + delta->num_temp_items); + } + + // TODO: pack temp stuff + + // finish + //mem_copy(delta->offsets, deleted, delta->num_deleted_items*sizeof(int)); + //mem_copy(&(delta->offsets[delta->num_deleted_items]), update, delta->num_update_items*sizeof(int)); + //mem_copy(&(delta->offsets[delta->num_deleted_items+delta->num_update_items]), temp, delta->num_temp_items*sizeof(int)); + //mem_copy(delta->data_start(), data, data_size); + //delta->data_size = data_size; + + if(!delta->num_deleted_items && !delta->num_update_items && !delta->num_temp_items) + return 0; + + return (int)((char*)data-(char*)dstdata); +} + +int snapshot_unpack_delta(snapshot *from, snapshot *to, void *srcdata, int data_size) +{ + snapshot_builder builder; + snapshot_delta *delta = (snapshot_delta *)srcdata; + int *data = (int *)delta->data; + + builder.start(); + + // unpack deleted stuff + int *deleted = data; + data += delta->num_deleted_items; + + // copy all non deleted stuff + for(int i = 0; i < from->num_items; i++) + { + //dbg_assert(0, "fail!"); + snapshot::item *fromitem = from->get_item(i); + int itemsize = from->get_item_datasize(i); + int keep = 1; + for(int d = 0; d < delta->num_deleted_items; d++) + { + if(deleted[d] == fromitem->key()) + { + keep = 0; + break; + } + } + + if(keep) + { + // keep it + int *newdata = (int *)(snapshot::item *)builder.new_item(fromitem->type(), fromitem->id(), itemsize); + mem_copy(newdata, fromitem->data(), itemsize); + } + } + + // unpack updated stuff + for(int i = 0; i < delta->num_update_items; i++) + { + int itemsize, id, type, key; + itemsize = *data++; + //key = *data++; + type = *data++; + id = *data++; + + key = (type<<16)|id; + + // create the item if needed + int *newdata = builder.get_item_data(key); + if(!newdata) + newdata = (int *)builder.new_item(key>>16, key&0xffff, itemsize); + + int fromindex = from->get_item_index(key); + if(fromindex != -1) + { + // we got an update so we need to apply the diff + int *pastdata = (int *)from->get_item(fromindex)->data(); + undiff_item(pastdata, data, newdata, itemsize/4); + } + else // no previous, just copy the data + mem_copy(newdata, data, itemsize); + + data += itemsize/4; + } + + // TODO: unpack temp stuff + + // finish up + return builder.finish(to); +} diff --git a/src/engine/snapshot.h b/src/engine/snapshot.h index 9d8034861..9af94a3bd 100644 --- a/src/engine/snapshot.h +++ b/src/engine/snapshot.h @@ -1,19 +1,47 @@ +#ifndef FILE_SNAPSHOT_H +#define FILE_SNAPSHOT_H struct snapshot { + int data_size; int num_items; - int offsets[1]; - + struct item { int type_and_id; - char data[1]; - + + int *data() { return (int *)(this+1); } int type() { return type_and_id>>16; } int id() { return type_and_id&(0xffff); } + int key() { return type_and_id; } }; + + int *offsets() { return (int *)(this+1); } + char *data_start() { return (char *)(offsets() + num_items); } + item *get_item(int index) { return (item *)(data_start() + offsets()[index]); }; - char *data_start() { return (char *)&offsets[num_items]; } - item *get_item(int index) { return (item *)(data_start() + offsets[index]); }; + // returns the number of ints in the item data + int get_item_datasize(int index) + { + if(index == num_items-1) + return (data_size - offsets()[index]) - sizeof(item); + return (offsets()[index+1] - offsets()[index]) - sizeof(item); + } + + int get_item_index(int key) + { + // TODO: this should not be a linear search. very bad + for(int i = 0; i < num_items; i++) + { + if(get_item(i)->key() == key) + return i; + } + return -1; + } }; +void *snapshot_empty_delta(); +int snapshot_create_delta(snapshot *from, snapshot *to, void *data); +int snapshot_unpack_delta(snapshot *from, snapshot *to, void *data, int data_size); + +#endif // FILE_SNAPSHOT_H diff --git a/src/engine/versions.h b/src/engine/versions.h index f5fceb484..7b4fdc2a4 100644 --- a/src/engine/versions.h +++ b/src/engine/versions.h @@ -1,3 +1,3 @@ #define TEEWARS_NETVERSION 0xffffffff //#define TEEWARS_NETVERSION_STRING "dev v2" -#define TEEWARS_VERSION "0.2.1-dev" +#define TEEWARS_VERSION "0.2.0-dev" diff --git a/src/game/client/game_client.cpp b/src/game/client/game_client.cpp index 2e95f1874..2f05aa77a 100644 --- a/src/game/client/game_client.cpp +++ b/src/game/client/game_client.cpp @@ -1,128 +1,46 @@ +#include #include #include #include #include "../game.h" #include "mapres_image.h" #include "mapres_tilemap.h" +#include "data.h" using namespace baselib; -static int texture_char_default = 0; -static int texture_game = 0; -static int texture_weapon = 0; -static int texture_sun = 0; -static int texture_particles = 0; +static data_container *data; -struct weapontexcell -{ - float x; - float y; - float w; - float h; -}; -struct renderparams -{ - float sizex; - float sizey; - float offsetx; - float offsety; -}; -int numcellsx = 32; -int numcellsy = 32; -renderparams weaponrenderparams[WEAPON_NUMWEAPONS]; -renderparams modifierrenderparams[WEAPON_NUMWEAPONS]; +int charids[16] = {2,10,0,4,12,6,14,1,9,15,13,11,7,5,8,3}; -weapontexcell weaponprojtexcoord[WEAPON_NUMWEAPONS]; -weapontexcell weapontexcoord[WEAPON_NUMWEAPONS]; -weapontexcell weapontexcoordcursor[WEAPON_NUMWEAPONS]; - -weapontexcell poweruptexcoord[POWERUP_TYPE_NUMPOWERUPS]; - -weapontexcell modifiertexcoord[MODIFIER_NUMMODIFIERS]; -weapontexcell modifiertexcoordcursor[MODIFIER_NUMMODIFIERS]; - -int nummuzzletex[WEAPON_NUMWEAPONS]; -weapontexcell muzzletexcoord[WEAPON_NUMWEAPONS][3]; -renderparams muzzleparams[WEAPON_NUMWEAPONS]; - -#define NUMHADOKENS 6 -#define NUMSTARS 2 -#define NUMPARTICLES 9 -int particlesnumcellsx = 16; -int particlesnumcellsy = 16; -weapontexcell chaintexcoord; -weapontexcell chainheadtexcoord; -weapontexcell stars[NUMSTARS]; - -float lifemodifier[NUMPARTICLES]; -vec4 particlecolors[NUMPARTICLES]; -weapontexcell particlestexcoord[NUMPARTICLES]; - -int charnumcellsx = 8; -int charnumcellsy = 32; -int charoffsety = 2; -weapontexcell body[2]; -weapontexcell leye; -weapontexcell reye; -weapontexcell feet[2]; - -int charids[16] = { 2,10,0,4,12,6,14,1,9,15,13,11,7,5,8,3 }; - -renderparams hadokenparams[6]; -weapontexcell hadoken[6]; - -float recoils[WEAPON_NUMWEAPONS] = { 10.0f, 10.0f, 10.0f, 10.0f }; - -static int font_texture = 0; static vec2 mouse_pos; - - static vec2 local_player_pos; static obj_player *local_player; -float frandom() -{ - return rand()/(float)(RAND_MAX); -} +inline float frandom() { return rand()/(float)(RAND_MAX); } -float sign(float f) +void snd_play_random(int setid, float vol, float pan) { - return f<0.0f?-1.0f:1.0f; -} - -// sound helpers -template -class sound_kit -{ -private: - int sounds[N]; - int last_id; -public: - sound_kit() : last_id(-1) { } + soundset *set = &data->sounds[setid]; - int& operator[](int id) { return sounds[id]; } - - inline void play_random(float vol = 1.0f, float pan = 0.0f); -}; - -template<> -inline void sound_kit<1>::play_random(float vol, float pan) -{ - snd_play(sounds[0], SND_PLAY_ONCE, vol, pan); -} - -template -inline void sound_kit::play_random(float vol, float pan) -{ + if(!set->num_sounds) + return; + + if(set->num_sounds == 1) + { + snd_play(set->sounds[0].id, SND_PLAY_ONCE, vol, pan); + return; + } + + // play a random one int id; do { - id = rand() % N; - } while(id == last_id); - snd_play(sounds[id], SND_PLAY_ONCE, vol, pan); - last_id = id; + id = rand() % set->num_sounds; + } while(id == set->last); + snd_play(set->sounds[id].id, SND_PLAY_ONCE, vol, pan); + set->last = id; } - // sound volume tweak static const float stereo_separation = 0.01f; static const float stereo_separation_deadzone = 512.0f; @@ -133,31 +51,6 @@ static const float volume_tee = 0.5f; static const float volume_hit = 0.5f; static const float volume_music = 0.8f; -// sounds -sound_kit<3> sound_gun_fire; -sound_kit<3> sound_shotty_fire; -sound_kit<3> sound_flump_launch; -sound_kit<3> sound_hammer_swing; -sound_kit<3> sound_ninja_attack; - -sound_kit<3> sound_flump_explode; -sound_kit<4> sound_ninja_hit; - -sound_kit<3> sound_weapon_switch; - -sound_kit<12> sound_pain_short; -sound_kit<2> sound_pain_long; - -sound_kit<4> sound_body_jump; -sound_kit<4> sound_body_land; -sound_kit<2> sound_body_splat; - -sound_kit<7> sound_spawn; -sound_kit<2> sound_tee_cry; - -sound_kit<1> sound_hook_loop; -sound_kit<3> sound_hook_attach; - void sound_vol_pan(const vec2& p, float *vol, float *pan) { vec2 player_to_ev = p - local_player_pos; @@ -181,56 +74,45 @@ void sound_vol_pan(const vec2& p, float *vol, float *pan) } } -// TODO: we should do something nicer then this -static void cell_select_ex(int cx, int cy, float x, float y, float w, float h) +enum { - gfx_quads_setsubset(x/(float)cx,y/(float)cy,(x+w)/(float)cx,(y+h)/(float)cy); -} - -/* -static void cell_select_ex_flip_x(int cx, int cy, float x, float y, float w, float h) -{ - gfx_quads_setsubset((x+w)/(float)cx,y/(float)cy,x /(float)cx,(y+h)/(float)cy); -}*/ - -static void cell_select_ex_flip_y(int cx, int cy, float x, float y, float w, float h) -{ - gfx_quads_setsubset(x/(float)cx,(y+h)/(float)cy,(x+w)/(float)cx,y/(float)cy); -} -/* -static void cell_select(int x, int y, int w, int h) -{ - gfx_quads_setsubset(x/16.0f,y/16.0f,(x+w)/16.0f,(y+h)/16.0f); -}*/ - -inline void cell_select_flip_x(int x, int y, int w, int h) -{ - gfx_quads_setsubset((x+w)/16.0f,y/16.0f,(x)/16.0f,(y+h)/16.0f); -} - -inline void cell_select_flip_y(int x, int y, int w, int h) -{ - gfx_quads_setsubset(x/16.0f,(y+h)/16.0f,(x+w)/16.0f,(y)/16.0f); -} - -struct particle -{ - vec2 pos; - vec2 vel; - float life; - float max_life; - float size; - - float rot; - float rotspeed; - - float gravity; - float friction; - int iparticle; - - vec4 color; + SPRITE_FLAG_FLIP_Y=1, }; +static float sprite_w_scale; +static float sprite_h_scale; + +static void select_sprite(sprite *spr, int flags=0, int sx=0, int sy=0) +{ + int x = spr->x+sx; + int y = spr->y+sy; + int w = spr->w; + int h = spr->h; + int cx = spr->set->gridx; + int cy = spr->set->gridy; + + float f = sqrtf(h*h + w*w); + sprite_w_scale = w/f; + sprite_h_scale = h/f; + + if(flags&SPRITE_FLAG_FLIP_Y) + gfx_quads_setsubset(x/(float)cx,(y+h)/(float)cy,(x+w)/(float)cx,y/(float)cy); + else + gfx_quads_setsubset(x/(float)cx,y/(float)cy,(x+w)/(float)cx,(y+h)/(float)cy); +} + +static void select_sprite(int id, int flags=0, int sx=0, int sy=0) +{ + if(id < 0 || id > data->num_sprites) + return; + select_sprite(&data->sprites[id], flags, sx, sy); +} + +static void draw_sprite(float x, float y, float size) +{ + gfx_quads_draw(x, y, size*sprite_w_scale, size*sprite_h_scale); +} + void move_point(vec2 *inout_pos, vec2 *inout_vel, float elasticity) { vec2 pos = *inout_pos; @@ -320,7 +202,7 @@ public: i->pos.x += ((float)rand()/(float)RAND_MAX) * 5.0f; i->amount = amount; i->life = 1.5f; - i->istar = rand() % NUMSTARS; + i->istar = rand()%2; i->vel = vec2(((float)rand()/(float)RAND_MAX) * 50.0f,-150.0f); i->startangle = (( (float)rand()/(float)RAND_MAX) - 1.0f) * 2.0f * pi; } @@ -336,7 +218,7 @@ public: lastupdate = time_get(); float delta = (float) (lastupdate - lasttime) / (float)time_freq(); - gfx_texture_set(texture_particles); + gfx_texture_set(data->images[IMAGE_PARTICLES].id); gfx_quads_begin(); for(int i = 0; i < num_items;) { @@ -351,21 +233,9 @@ public: gfx_quads_setcolor(1.0f,1.0f,1.0f, items[i].life / 1.5f); gfx_quads_setrotation(items[i].startangle + items[i].life * 2.0f); float size = 64.0f; - cell_select_ex(particlesnumcellsx,particlesnumcellsy, stars[items[i].istar].x,stars[items[i].istar].y, stars[items[i].istar].w, stars[items[i].istar].h); + const int stars[] = {SPRITE_STAR1, SPRITE_STAR2}; + select_sprite(stars[items[i].istar]); gfx_quads_draw(items[i].pos.x-size/2, items[i].pos.y-size/2, size, size); - /*char buf[32]; - if(items[i].amount < 0) - { - sprintf(buf, "%d", items[i].amount*-1); - } - else - { - sprintf(buf, "%d", items[i].amount); - } - float size = 42.0f; - if(items[i].life > 1.25f) - size += 42.0f * ((items[i].life - 1.25f) * 4); - gfx_quads_text(items[i].pos.x-size/2, items[i].pos.y, size, buf);*/ i++; } } @@ -374,202 +244,29 @@ public: }; -/*class texture_animator -{ -public: - - int texture; - int numframes; - float duration; - float* framereltime; - weapontexcell* params; - texture_animator() - { - texture = -1; - numframes = 0; - duration = 0; - framereltime = 0; - params = 0; - } - - ~texture_animator() - { - if (params) - mem_free(params); - if (framereltime) - mem_free(framereltime); - } - - void create_anim(int texture, int numframes, float duration) - { - framereltime = 0; - params = 0; - this->texture = texture; - this->numframes = numframes; - this->duration = duration; - if (numframes) - { - framereltime = (float*)mem_alloc(sizeof(float) * numframes,1); - params = (weapontexcell*)mem_alloc(sizeof(renderparams) * numframes,1); - float delta = 1.0f / (float)(numframes - 1); - for (int i = 0; i < numframes; i++) - { - framereltime[i] = delta * i; - } - } - } - - static void create_gunmuzzle(texture_animator& anim, int texture, float duration) - { - anim.create_anim(texture, 3, duration); - anim.params[0].x = 8; - anim.params[0].y = 4; - anim.params[0].w = 3; - anim.params[0].h = 2; - anim.params[1].x = 12; - anim.params[1].y = 4; - anim.params[1].w = 3; - anim.params[1].h = 2; - anim.params[2].x = 16; - anim.params[2].y = 4; - anim.params[2].w = 3; - anim.params[2].h = 2; - } - - static void create_shotgunmuzzle() - { - - } -};*/ - -class keyframe -{ -public: - vec2 pos; - float angle; - float relativetime; -}; - -class anim -{ -public: - keyframe* keyframes; - int numframes; - float duration; - anim() - { - numframes = 0; - keyframes = 0; - } - ~anim() - { - if (keyframes) - mem_free(keyframes); - } - - void create_anim(int numframes, float duration) - { - if (keyframes) - mem_free(keyframes); - - this->numframes = numframes; - this->duration = duration; - keyframes = (keyframe*)mem_alloc(sizeof(keyframe) * numframes,1); - float delta = 1.0f / (float) (numframes - 1); - for (int i = 0; i < numframes; i++) - { - keyframes[i].pos = vec2(0.0f,0.0f); - keyframes[i].angle = 0; - keyframes[i].relativetime = delta * (float)i; - } - } - - void getframes(float relativetime, keyframe*& frame1, keyframe*& frame2, float& blend) - { - for (int i = 1; i < numframes; i++) - { - if (keyframes[i-1].relativetime <= relativetime && keyframes[i].relativetime >= relativetime) - { - frame1 = &keyframes[i-1]; - frame2 = &keyframes[i]; - blend = (relativetime - frame1->relativetime) / (frame2->relativetime - frame1->relativetime); - } - } - } - - void evalanim(float time, vec2& pos, float& angle) - { - float reltime = max(0.0f, min(1.0f, time / duration)); - keyframe* frame1 = 0; - keyframe* frame2 = 0; - float blend = 0.0f; - getframes(reltime, frame1, frame2, blend); - - if (frame1 && frame2) - { - pos = mix(frame1->pos, frame2->pos, blend); - angle = LERP(frame1->angle, frame2->angle, blend); - } - } - - static void setup_hammer(anim& hammeranim) - { - // straight up = -0.25 - // frame0 = standard pose time 0 - // frame1 = back a little time 0.3 - // frame2 = over head time 0.4 - // frame3 = on ground smashed time 0.5 - // frame4 = back to standard pose time 1.0 - hammeranim.create_anim(5, 1.0f); - // only angles... (for now...) - hammeranim.keyframes[0].angle = -0.35f * pi * 2.0f; - hammeranim.keyframes[1].angle = -0.4f * pi * 2.0f; - hammeranim.keyframes[1].relativetime = 0.3f; - hammeranim.keyframes[2].angle = -0.25f; - hammeranim.keyframes[2].relativetime = 0.4f; - hammeranim.keyframes[3].angle = 0.0f * pi * 2.0f; - hammeranim.keyframes[3].relativetime = 0.5f; - hammeranim.keyframes[4].angle = -0.35f * pi * 2.0f; - hammeranim.keyframes[4].relativetime = 1.0f; - } - - static void setup_ninja(anim& ninjanim) - { - // (straight up = -0.25) - // frame0 = standard pose straight back time 0.0 - // frame1 = overhead attack frame 1 time 0.1 - // frame2 = attack end frame time 0.15 - // frame3 = attack hold frame (a bit up) time 0.4 - // frame4 = attack hold frame end time 0.7 - // frame5 = endframe time 1.0 - ninjanim.create_anim(6, 1.0f); - // only angles... (for now...) - ninjanim.keyframes[0].angle = -0.5f * pi * 2.0f; - - ninjanim.keyframes[1].angle = -0.3f * pi * 2.0f; - ninjanim.keyframes[1].relativetime = 0.1f; - - ninjanim.keyframes[2].angle = 0.1f * pi * 2.0f; - ninjanim.keyframes[2].relativetime = 0.15f; - - ninjanim.keyframes[3].angle = -0.05f * pi * 2.0f; - ninjanim.keyframes[3].relativetime = 0.42; - - ninjanim.keyframes[4].angle = -0.05f * pi * 2.0f; - ninjanim.keyframes[4].relativetime = 0.5f; - - ninjanim.keyframes[5].angle = -0.5f * pi * 2.0f; - ninjanim.keyframes[5].relativetime = 1.0f; - } -}; - -static anim hammeranim; -static anim ninjaanim; static health_texts healthmods; class particle_system { public: + struct particle + { + vec2 pos; + vec2 vel; + float life; + float max_life; + float size; + + float rot; + float rotspeed; + + float gravity; + float friction; + int iparticle; + + vec4 color; + }; + enum { MAX_PARTICLES=1024, @@ -588,10 +285,10 @@ public: if (num_particles >= MAX_PARTICLES) return; - particles[num_particles].iparticle = rand() % NUMPARTICLES; + particles[num_particles].iparticle = rand() % data->num_particles; particles[num_particles].pos = pos; particles[num_particles].vel = vel; - particles[num_particles].life = life - lifemodifier[particles[num_particles].iparticle] * life; + particles[num_particles].life = life - (data->particles[particles[num_particles].iparticle].lifemod/100.0f) * life; particles[num_particles].size = size; particles[num_particles].max_life = life; particles[num_particles].gravity = gravity; @@ -626,23 +323,24 @@ public: void render() { gfx_blend_additive(); - gfx_texture_set(texture_particles); + gfx_texture_set(data->images[IMAGE_PARTICLES].id); gfx_quads_begin(); - //cell_select(4,1,1,1); - //cell_select(0,6,2,2); - //gfx_quads_setrotation(get_angle(vec2(proj->vx, proj->vy))); + for(int i = 0; i < num_particles; i++) { int type = particles[i].iparticle; - cell_select_ex(particlesnumcellsx,particlesnumcellsy,particlestexcoord[type].x, particlestexcoord[type].y, particlestexcoord[type].w, particlestexcoord[type].h); + select_sprite(data->particles[type].spr); float a = 1 - particles[i].life / particles[i].max_life; vec2 p = particles[i].pos; - //a *= length(particles[i].vel) * 0.01f; + gfx_quads_setrotation(particles[i].rot); - gfx_quads_setcolor(particlecolors[type].x,particlecolors[type].y,particlecolors[type].z,pow(a,0.75f)); - //gfx_quads_setcolor(particlecolors[type].x * 0.5,particlecolors[type].y * 0.5,particlecolors[type].z* 0.5,pow(a,0.75f)); - //gfx_quads_setcolor(particlecolors[type].x * 0.0,particlecolors[type].y * 0.0,particlecolors[type].z* 0.0,pow(a,0.75f)); - //gfx_quads_setcolor(0.64f*2,0.28f*2,0.16f*2,pow(a,0.75f)); + + gfx_quads_setcolor( + data->particles[type].color_r, + data->particles[type].color_g, + data->particles[type].color_b, + pow(a, 0.75f)); + gfx_quads_draw(p.x, p.y,particles[i].size,particles[i].size); } gfx_quads_end(); @@ -654,447 +352,17 @@ static particle_system temp_system; void modc_init() { - // load textures - texture_weapon = gfx_load_texture("data/tileset_weapons.png"); - texture_game = gfx_load_texture("data/game_main.png"); - texture_char_default = gfx_load_texture("data/char_teefault.png"); - texture_sun = gfx_load_texture("data/sun.png"); - texture_particles = gfx_load_texture("data/tileset_particles.png"); - font_texture = gfx_load_texture("data/debug_font.png"); - + // load the data container + data = load_data_container("data/client.dat"); // load sounds - sound_gun_fire[0] = snd_load_wav("data/audio/wp_gun_fire-01.wav"); - sound_gun_fire[0] = snd_load_wav("data/audio/wp_gun_fire-01.wav"); - sound_gun_fire[1] = snd_load_wav("data/audio/wp_gun_fire-02.wav"); - sound_shotty_fire[0] = snd_load_wav("data/audio/wp_shotty_fire-01.wav"); - sound_shotty_fire[1] = snd_load_wav("data/audio/wp_shotty_fire-02.wav"); - sound_shotty_fire[2] = snd_load_wav("data/audio/wp_shotty_fire-03.wav"); - sound_flump_launch[0] = snd_load_wav("data/audio/wp_flump_launch-01.wav"); - sound_flump_launch[1] = snd_load_wav("data/audio/wp_flump_launch-02.wav"); - sound_flump_launch[2] = snd_load_wav("data/audio/wp_flump_launch-03.wav"); - sound_hammer_swing[0] = snd_load_wav("data/audio/wp_hammer_swing-01.wav"); - sound_hammer_swing[1] = snd_load_wav("data/audio/wp_hammer_swing-02.wav"); - sound_hammer_swing[2] = snd_load_wav("data/audio/wp_hammer_swing-03.wav"); - sound_ninja_attack[0] = snd_load_wav("data/audio/wp_ninja_attack-01.wav"); - sound_ninja_attack[1] = snd_load_wav("data/audio/wp_ninja_attack-02.wav"); - sound_ninja_attack[2] = snd_load_wav("data/audio/wp_ninja_attack-03.wav"); - - sound_flump_explode[0] = snd_load_wav("data/audio/wp_flump_explo-01.wav"); - sound_flump_explode[1] = snd_load_wav("data/audio/wp_flump_explo-02.wav"); - sound_flump_explode[2] = snd_load_wav("data/audio/wp_flump_explo-03.wav"); - sound_ninja_hit[0] = snd_load_wav("data/audio/wp_ninja_hit-01.wav"); - sound_ninja_hit[1] = snd_load_wav("data/audio/wp_ninja_hit-02.wav"); - sound_ninja_hit[2] = snd_load_wav("data/audio/wp_ninja_hit-03.wav"); - sound_ninja_hit[3] = snd_load_wav("data/audio/wp_ninja_hit-04.wav"); - - sound_weapon_switch[0] = snd_load_wav("data/audio/wp_switch-01.wav"); - sound_weapon_switch[1] = snd_load_wav("data/audio/wp_switch-02.wav"); - sound_weapon_switch[2] = snd_load_wav("data/audio/wp_switch-03.wav"); - - sound_pain_short[0] = snd_load_wav("data/audio/vo_teefault_pain_short-01.wav"); - sound_pain_short[1] = snd_load_wav("data/audio/vo_teefault_pain_short-02.wav"); - sound_pain_short[2] = snd_load_wav("data/audio/vo_teefault_pain_short-03.wav"); - sound_pain_short[3] = snd_load_wav("data/audio/vo_teefault_pain_short-04.wav"); - sound_pain_short[4] = snd_load_wav("data/audio/vo_teefault_pain_short-05.wav"); - sound_pain_short[5] = snd_load_wav("data/audio/vo_teefault_pain_short-06.wav"); - sound_pain_short[6] = snd_load_wav("data/audio/vo_teefault_pain_short-07.wav"); - sound_pain_short[7] = snd_load_wav("data/audio/vo_teefault_pain_short-08.wav"); - sound_pain_short[8] = snd_load_wav("data/audio/vo_teefault_pain_short-09.wav"); - sound_pain_short[9] = snd_load_wav("data/audio/vo_teefault_pain_short-10.wav"); - sound_pain_short[10] = snd_load_wav("data/audio/vo_teefault_pain_short-11.wav"); - sound_pain_short[11] = snd_load_wav("data/audio/vo_teefault_pain_short-12.wav"); - - sound_pain_long[0] = snd_load_wav("data/audio/vo_teefault_pain_long-01.wav"); - sound_pain_long[1] = snd_load_wav("data/audio/vo_teefault_pain_long-02.wav"); - - sound_body_land[0] = snd_load_wav("data/audio/foley_land-01.wav"); - sound_body_land[1] = snd_load_wav("data/audio/foley_land-02.wav"); - sound_body_land[2] = snd_load_wav("data/audio/foley_land-03.wav"); - sound_body_land[3] = snd_load_wav("data/audio/foley_land-04.wav"); - sound_body_jump[0] = snd_load_wav("data/audio/foley_foot_left-01.wav"); - sound_body_jump[1] = snd_load_wav("data/audio/foley_foot_left-02.wav"); - sound_body_jump[2] = snd_load_wav("data/audio/foley_foot_left-03.wav"); - sound_body_jump[3] = snd_load_wav("data/audio/foley_foot_left-04.wav"); - sound_body_jump[4] = snd_load_wav("data/audio/foley_foot_right-01.wav"); - sound_body_jump[5] = snd_load_wav("data/audio/foley_foot_right-02.wav"); - sound_body_jump[6] = snd_load_wav("data/audio/foley_foot_right-03.wav"); - sound_body_jump[7] = snd_load_wav("data/audio/foley_foot_right-04.wav"); - - sound_body_splat[1] = snd_load_wav("data/audio/foley_body_splat-02.wav"); - sound_body_splat[2] = snd_load_wav("data/audio/foley_body_splat-03.wav"); - sound_body_splat[3] = snd_load_wav("data/audio/foley_body_splat-04.wav"); - - sound_spawn[0] = snd_load_wav("data/audio/vo_teefault_spawn-01.wav"); - sound_spawn[1] = snd_load_wav("data/audio/vo_teefault_spawn-02.wav"); - sound_spawn[2] = snd_load_wav("data/audio/vo_teefault_spawn-03.wav"); - sound_spawn[3] = snd_load_wav("data/audio/vo_teefault_spawn-04.wav"); - sound_spawn[4] = snd_load_wav("data/audio/vo_teefault_spawn-05.wav"); - sound_spawn[5] = snd_load_wav("data/audio/vo_teefault_spawn-06.wav"); - sound_spawn[6] = snd_load_wav("data/audio/vo_teefault_spawn-07.wav"); - - sound_tee_cry[0] = snd_load_wav("data/audio/vo_teefault_cry-01.wav"); - sound_tee_cry[1] = snd_load_wav("data/audio/vo_teefault_cry-02.wav"); - - //sound_hook_loop[0] = snd_load_wav("data/audio/hook_loop-01.wav"); - sound_hook_loop[0] = snd_load_wav("data/audio/hook_loop-02.wav"); - sound_hook_attach[0] = snd_load_wav("data/audio/hook_attach-01.wav"); - sound_hook_attach[1] = snd_load_wav("data/audio/hook_attach-02.wav"); - sound_hook_attach[2] = snd_load_wav("data/audio/hook_attach-03.wav"); - - poweruptexcoord[POWERUP_TYPE_HEALTH].x = 10; - poweruptexcoord[POWERUP_TYPE_HEALTH].y = 2; - poweruptexcoord[POWERUP_TYPE_HEALTH].w = 2; - poweruptexcoord[POWERUP_TYPE_HEALTH].h = 2; - - poweruptexcoord[POWERUP_TYPE_ARMOR].x = 12; - poweruptexcoord[POWERUP_TYPE_ARMOR].y = 2; - poweruptexcoord[POWERUP_TYPE_ARMOR].w = 2; - poweruptexcoord[POWERUP_TYPE_ARMOR].h = 2; + for(int s = 0; s < data->num_sounds; s++) + for(int i = 0; i < data->sounds[s].num_sounds; i++) + data->sounds[s].sounds[i].id = snd_load_wav(data->sounds[s].sounds[i].filename); - poweruptexcoord[POWERUP_TYPE_WEAPON].x = 3; - poweruptexcoord[POWERUP_TYPE_WEAPON].y = 0; - poweruptexcoord[POWERUP_TYPE_WEAPON].w = 6; - poweruptexcoord[POWERUP_TYPE_WEAPON].h = 2; - - poweruptexcoord[POWERUP_TYPE_NINJA].x = 3; - poweruptexcoord[POWERUP_TYPE_NINJA].y = 10; - poweruptexcoord[POWERUP_TYPE_NINJA].w = 7; - poweruptexcoord[POWERUP_TYPE_NINJA].h = 2; - - poweruptexcoord[POWERUP_TYPE_TIMEFIELD].x = 3; - poweruptexcoord[POWERUP_TYPE_TIMEFIELD].y = 0; - poweruptexcoord[POWERUP_TYPE_TIMEFIELD].w = 6; - poweruptexcoord[POWERUP_TYPE_TIMEFIELD].h = 2; - - // Setup weapon cell coords - float sizemodifier = 1.0f; - weaponrenderparams[WEAPON_TYPE_GUN].sizex = 60.0f * sizemodifier; - weaponrenderparams[WEAPON_TYPE_GUN].sizey = 30.0f * sizemodifier; - weaponrenderparams[WEAPON_TYPE_GUN].offsetx = 32.0f; - weaponrenderparams[WEAPON_TYPE_GUN].offsety = 4.0f; - weapontexcoordcursor[WEAPON_TYPE_GUN].x = 0; - weapontexcoordcursor[WEAPON_TYPE_GUN].y = 4; - weapontexcoordcursor[WEAPON_TYPE_GUN].w = 2; - weapontexcoordcursor[WEAPON_TYPE_GUN].h = 2; - weapontexcoord[WEAPON_TYPE_GUN].x = 2; - weapontexcoord[WEAPON_TYPE_GUN].y = 4; - weapontexcoord[WEAPON_TYPE_GUN].w = 4; - weapontexcoord[WEAPON_TYPE_GUN].h = 2; - weaponprojtexcoord[WEAPON_TYPE_GUN].x = 6; - weaponprojtexcoord[WEAPON_TYPE_GUN].y = 4; - weaponprojtexcoord[WEAPON_TYPE_GUN].w = 2; - weaponprojtexcoord[WEAPON_TYPE_GUN].h = 2; - - nummuzzletex[WEAPON_TYPE_GUN] = 3; - muzzletexcoord[WEAPON_TYPE_GUN][0].x = 8; - muzzletexcoord[WEAPON_TYPE_GUN][0].y = 4; - muzzletexcoord[WEAPON_TYPE_GUN][0].w = 3; - muzzletexcoord[WEAPON_TYPE_GUN][0].h = 2; - muzzletexcoord[WEAPON_TYPE_GUN][1].x = 12; - muzzletexcoord[WEAPON_TYPE_GUN][1].y = 4; - muzzletexcoord[WEAPON_TYPE_GUN][1].w = 3; - muzzletexcoord[WEAPON_TYPE_GUN][1].h = 2; - muzzletexcoord[WEAPON_TYPE_GUN][2].x = 16; - muzzletexcoord[WEAPON_TYPE_GUN][2].y = 4; - muzzletexcoord[WEAPON_TYPE_GUN][2].w = 3; - muzzletexcoord[WEAPON_TYPE_GUN][2].h = 2; - - muzzleparams[WEAPON_TYPE_GUN].sizex = 60.0f * sizemodifier; - muzzleparams[WEAPON_TYPE_GUN].sizey = 40.0f * sizemodifier; - muzzleparams[WEAPON_TYPE_GUN].offsetx = 50.0f * sizemodifier; - muzzleparams[WEAPON_TYPE_GUN].offsety = 6.0f * sizemodifier; - - sizemodifier = 1.3f; - weaponrenderparams[WEAPON_TYPE_ROCKET].sizex = 70.0f * sizemodifier; - weaponrenderparams[WEAPON_TYPE_ROCKET].sizey = 20.0f * sizemodifier; - weaponrenderparams[WEAPON_TYPE_ROCKET].offsetx = 24.0f; - weaponrenderparams[WEAPON_TYPE_ROCKET].offsety = -2.0f; - weapontexcoordcursor[WEAPON_TYPE_ROCKET].x = 0; - weapontexcoordcursor[WEAPON_TYPE_ROCKET].y = 8; - weapontexcoordcursor[WEAPON_TYPE_ROCKET].w = 2; - weapontexcoordcursor[WEAPON_TYPE_ROCKET].h = 2; - weapontexcoord[WEAPON_TYPE_ROCKET].x = 2; - weapontexcoord[WEAPON_TYPE_ROCKET].y = 8; - weapontexcoord[WEAPON_TYPE_ROCKET].w = 7; - weapontexcoord[WEAPON_TYPE_ROCKET].h = 2; - weaponprojtexcoord[WEAPON_TYPE_ROCKET].x = 10; - weaponprojtexcoord[WEAPON_TYPE_ROCKET].y = 8; - weaponprojtexcoord[WEAPON_TYPE_ROCKET].w = 2; - weaponprojtexcoord[WEAPON_TYPE_ROCKET].h = 2; - - /*weaponrenderparams[WEAPON_TYPE_SNIPER].sizex = 60.0f; - weaponrenderparams[WEAPON_TYPE_SNIPER].sizey = 20.0f; - weaponrenderparams[WEAPON_TYPE_SNIPER].offsetx = 16.0f; - weaponrenderparams[WEAPON_TYPE_SNIPER].offsety = 4.0f; - weapontexcoordcursor[WEAPON_TYPE_SNIPER].x = 0; - weapontexcoordcursor[WEAPON_TYPE_SNIPER].y = 6; - weapontexcoordcursor[WEAPON_TYPE_SNIPER].w = 2; - weapontexcoordcursor[WEAPON_TYPE_SNIPER].h = 2; - weapontexcoord[WEAPON_TYPE_SNIPER].x = 3; - weapontexcoord[WEAPON_TYPE_SNIPER].y = 6; - weapontexcoord[WEAPON_TYPE_SNIPER].w = 6; - weapontexcoord[WEAPON_TYPE_SNIPER].h = 2; - weaponprojtexcoord[WEAPON_TYPE_SNIPER].x = 10; - weaponprojtexcoord[WEAPON_TYPE_SNIPER].y = 6; - weaponprojtexcoord[WEAPON_TYPE_SNIPER].w = 1; - weaponprojtexcoord[WEAPON_TYPE_SNIPER].h = 1;*/ - - weaponrenderparams[WEAPON_TYPE_SHOTGUN].sizex = 80.0f * sizemodifier; - weaponrenderparams[WEAPON_TYPE_SHOTGUN].sizey = 20.0f * sizemodifier; - weaponrenderparams[WEAPON_TYPE_SHOTGUN].offsetx = 24.0f; - weaponrenderparams[WEAPON_TYPE_SHOTGUN].offsety = -2.0f; - weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].x = 0; - weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].y = 6; - weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].w = 2; - weapontexcoordcursor[WEAPON_TYPE_SHOTGUN].h = 2; - weapontexcoord[WEAPON_TYPE_SHOTGUN].x = 2; - weapontexcoord[WEAPON_TYPE_SHOTGUN].y = 6; - weapontexcoord[WEAPON_TYPE_SHOTGUN].w = 8; - weapontexcoord[WEAPON_TYPE_SHOTGUN].h = 2; - weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].x = 10; - weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].y = 6; - weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].w = 2; - weaponprojtexcoord[WEAPON_TYPE_SHOTGUN].h = 2; - - nummuzzletex[WEAPON_TYPE_SHOTGUN] = 3; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].x = 12; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].y = 6; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].w = 3; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][0].h = 2; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].x = 16; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].y = 6; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].w = 3; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][1].h = 2; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].x = 20; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].y = 6; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].w = 3; - muzzletexcoord[WEAPON_TYPE_SHOTGUN][2].h = 2; - - muzzleparams[WEAPON_TYPE_SHOTGUN].sizex = 60.0f * sizemodifier; - muzzleparams[WEAPON_TYPE_SHOTGUN].sizey = 40.0f * sizemodifier; - muzzleparams[WEAPON_TYPE_SHOTGUN].offsetx = 50.0f * sizemodifier; - muzzleparams[WEAPON_TYPE_SHOTGUN].offsety = 6.0f * sizemodifier; - - - - weaponrenderparams[WEAPON_TYPE_MELEE].sizex = 60.0f * sizemodifier; - weaponrenderparams[WEAPON_TYPE_MELEE].sizey = 50.0f * sizemodifier; - weaponrenderparams[WEAPON_TYPE_MELEE].offsetx = 20.0f; - weaponrenderparams[WEAPON_TYPE_MELEE].offsety = -4.0f; - weapontexcoordcursor[WEAPON_TYPE_MELEE].x = 0; - weapontexcoordcursor[WEAPON_TYPE_MELEE].y = 0; - weapontexcoordcursor[WEAPON_TYPE_MELEE].w = 2; - weapontexcoordcursor[WEAPON_TYPE_MELEE].h = 2; - weapontexcoord[WEAPON_TYPE_MELEE].x = 2; - weapontexcoord[WEAPON_TYPE_MELEE].y = 1; - weapontexcoord[WEAPON_TYPE_MELEE].w = 4; - weapontexcoord[WEAPON_TYPE_MELEE].h = 3; - weaponprojtexcoord[WEAPON_TYPE_MELEE].x = 0; - weaponprojtexcoord[WEAPON_TYPE_MELEE].y = 0; - weaponprojtexcoord[WEAPON_TYPE_MELEE].w = 0; - weaponprojtexcoord[WEAPON_TYPE_MELEE].h = 0; - - - // MODIFIERS - sizemodifier = 2.0; - modifierrenderparams[MODIFIER_TYPE_NINJA].sizex = 60.0f * sizemodifier; - modifierrenderparams[MODIFIER_TYPE_NINJA].sizey = 20.0f * sizemodifier; - modifierrenderparams[MODIFIER_TYPE_NINJA].offsetx = 20.0f; - modifierrenderparams[MODIFIER_TYPE_NINJA].offsety = 4.0f; - modifiertexcoord[MODIFIER_TYPE_NINJA].x = 2; - modifiertexcoord[MODIFIER_TYPE_NINJA].y = 10; - modifiertexcoord[MODIFIER_TYPE_NINJA].w = 7; - modifiertexcoord[MODIFIER_TYPE_NINJA].h = 2; - modifiertexcoordcursor[MODIFIER_TYPE_NINJA].x = 0; - modifiertexcoordcursor[MODIFIER_TYPE_NINJA].y = 10; - modifiertexcoordcursor[MODIFIER_TYPE_NINJA].w = 2; - modifiertexcoordcursor[MODIFIER_TYPE_NINJA].h = 2; - - modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].sizex = 60.0f * sizemodifier; - modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].sizey = 20.0f * sizemodifier; - modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].offsetx = 16.0f; - modifierrenderparams[MODIFIER_TYPE_TIMEFIELD].offsety = 4.0f; - modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].x = 0; - modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].y = 0; - modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].w = 0; - modifiertexcoord[MODIFIER_TYPE_TIMEFIELD].h = 0; - - stars[0].x = 0; - stars[0].y = 0; - stars[0].w = 2; - stars[0].h = 2; - - stars[1].x = 0; - stars[1].y = 2; - stars[1].w = 2; - stars[1].h = 2; - - particlecolors[0].x = 0.7f; - particlecolors[0].y = 0.7f; - particlecolors[0].z = 0.7f; - particlecolors[0].w = 1.0f; - particlestexcoord[0].x = 2; - particlestexcoord[0].y = 0; - particlestexcoord[0].w = 2; - particlestexcoord[0].h = 2; - particlecolors[1].x = 1.0f; - particlecolors[1].y = 1.0f; - particlecolors[1].z = 1.0f; - particlecolors[1].w = 1.0f; - particlestexcoord[1].x = 4; - particlestexcoord[1].y = 0; - particlestexcoord[1].w = 2; - particlestexcoord[1].h = 2; - particlecolors[2].x = 0.8f; - particlecolors[2].y = 0.8f; - particlecolors[2].z = 0.8f; - particlecolors[2].w = 1.0f; - particlestexcoord[2].x = 6; - particlestexcoord[2].y = 0; - particlestexcoord[2].w = 2; - particlestexcoord[2].h = 2; - particlecolors[3].x = 0.988f; - particlecolors[3].y = 1.0f; - particlecolors[3].z = 0.16f; - particlecolors[3].w = 1.0f; - particlestexcoord[3].x = 8; - particlestexcoord[3].y = 0; - particlestexcoord[3].w = 2; - particlestexcoord[3].h = 2; - particlecolors[4].x = 1.0f; - particlecolors[4].y = 1.0f; - particlecolors[4].z = 1.0f; - particlecolors[4].w = 1.0f; - particlestexcoord[4].x = 10; - particlestexcoord[4].y = 0; - particlestexcoord[4].w = 2; - particlestexcoord[4].h = 2; - particlecolors[5].x = 0.6f; - particlecolors[5].y = 0.6f; - particlecolors[5].z = 0.6f; - particlecolors[5].w = 1.0f; - particlestexcoord[5].x = 2; - particlestexcoord[5].y = 2; - particlestexcoord[5].w = 2; - particlestexcoord[5].h = 2; - particlecolors[6].x = 1.0f; - particlecolors[6].y = 1.0f; - particlecolors[6].z = 1.0f; - particlecolors[6].w = 1.0f; - particlestexcoord[6].x = 4; - particlestexcoord[6].y = 2; - particlestexcoord[6].w = 2; - particlestexcoord[6].h = 2; - particlecolors[5].x = 0.9f; - particlecolors[5].y = 0.9f; - particlecolors[5].z = 0.9f; - particlecolors[5].w = 1.0f; - particlestexcoord[7].x = 6; - particlestexcoord[7].y = 2; - particlestexcoord[7].w = 2; - particlestexcoord[7].h = 2; - particlecolors[8].x = 1.0f; - particlecolors[8].y = 1.0f; - particlecolors[8].z = 1.0f; - particlecolors[8].w = 1.0f; - particlestexcoord[8].x = 8; - particlestexcoord[8].y = 2; - particlestexcoord[8].w = 2; - particlestexcoord[8].h = 2; - lifemodifier[0] = 0.5f; - lifemodifier[1] = 0.5f; - lifemodifier[2] = 0.5f; - lifemodifier[3] = 0.7f; - lifemodifier[4] = 0.7f; - lifemodifier[5] = 1.0f; - lifemodifier[6] = 1.0f; - lifemodifier[7] = 1.5f; - lifemodifier[8] = 0.4f; - - chaintexcoord.x = 2; - chaintexcoord.y = 0; - chaintexcoord.w = 1; - chaintexcoord.h = 1; - - chainheadtexcoord.x = 3; - chainheadtexcoord.y = 0; - chainheadtexcoord.w = 2; - chainheadtexcoord.h = 1; - - - // anims - anim::setup_hammer(hammeranim); - anim::setup_ninja(ninjaanim); - - for (int i = 0; i < NUMHADOKENS; i++) - { - hadoken[i].x = 1; - hadoken[i].y = 12; - hadoken[i].w = 7; - hadoken[i].h = 4; - hadokenparams[i].sizex = 0.0f; - hadokenparams[i].sizey = 0.0f; - hadokenparams[i].offsetx = 0.0f; - hadokenparams[i].offsety = 0.0f;//-hadokenparams[0].sizey * 0.15f; - } - - // hadoken - hadoken[0].x = 1; - hadoken[0].y = 12; - hadoken[0].w = 7; - hadoken[0].h = 4; - hadokenparams[0].sizex = 70.0f * 2.5f; - hadokenparams[0].sizey = 40.0f * 2.5f; - hadokenparams[0].offsetx = -60.0f; - hadokenparams[0].offsety = 0;//-hadokenparams[0].sizey * 0.15f; - - hadoken[2].x = 8; - hadoken[2].y = 12; - hadoken[2].w = 8; - hadoken[2].h = 4; - hadokenparams[2].sizex = 80.0f * 2.5f; - hadokenparams[2].sizey = 40.0f * 2.5f; - hadokenparams[2].offsetx = -60.0f; - hadokenparams[2].offsety = 0;//-hadokenparams[1].sizey * 0.5f; - - hadoken[4].x = 17; - hadoken[4].y = 12; - hadoken[4].w = 7; - hadoken[4].h = 4; - hadokenparams[4].sizex = 70.0f * 2.5f; - hadokenparams[4].sizey = 40.0f * 2.5f; - hadokenparams[4].offsetx = -60.0f; - hadokenparams[4].offsety = 0;//-hadokenparams[2].sizey * 0.5f; - - // 0 = outline, 1 = body - body[0].x = 2; - body[0].y = 0; - body[0].w = 2; - body[0].h = 2; - body[1].x = 0; - body[1].y = 0; - body[1].w = 2; - body[1].h = 2; - - feet[0].x = 4; - feet[0].y = 1; - feet[0].w = 1; - feet[0].h = 0.5; - feet[1].x = 4; - feet[1].y = 1.52; - feet[1].w = 1; - feet[1].h = 0.48; - - leye.x = 5; - leye.y = 1; - leye.w = 0.5; - leye.h = 0.5; - - reye.x = 5; - reye.y = 1.0; - reye.w = 0.5; - reye.h = 0.5; + // load textures + for(int i = 0; i < data->num_images; i++) + data->images[i].id = gfx_load_texture(data->images[i].filename); } void modc_entergame() @@ -1179,77 +447,17 @@ void modc_newsnapshot() { ev_sound *ev = (ev_sound *)data; vec2 p(ev->x, ev->y); - int sound = (ev->sound & SOUND_MASK); + int soundid = ev->sound; //(ev->sound & SOUND_MASK); //bool bstartloop = (ev->sound & SOUND_LOOPFLAG_STARTLOOP) != 0; //bool bstoploop = (ev->sound & SOUND_LOOPFLAG_STOPLOOP) != 0; float vol, pan; sound_vol_pan(p, &vol, &pan); - - switch(sound) + + if(soundid >= 0 && soundid < NUM_SOUNDS) { - - // FIRE! - case SOUND_FIRE_GUN: - sound_gun_fire.play_random(volume_gun*vol, pan); - break; - case SOUND_FIRE_SHOTGUN: - sound_shotty_fire.play_random(volume_gun*vol, pan); - break; - case SOUND_FIRE_ROCKET: - sound_flump_launch.play_random(volume_gun*vol, pan); - break; - case SOUND_FIRE_MELEE: - sound_hammer_swing.play_random(volume_gun*vol, pan); - break; - case SOUND_FIRE_NINJA: - sound_ninja_attack.play_random(volume_gun*vol, pan); - break; - - // IMPACT - case SOUND_IMPACT_PROJECTILE_GUN: - break; - case SOUND_IMPACT_PROJECTILE_SHOTGUN: - break; - case SOUND_IMPACT_PROJECTILE_ROCKET: - sound_flump_explode.play_random(volume_hit*vol, pan); - break; - - // PLAYER - case SOUND_PLAYER_JUMP: - sound_body_jump.play_random(volume_tee*vol, pan); - break; - case SOUND_PLAYER_HURT_SHORT: - sound_pain_short.play_random(volume_tee*vol, pan); - break; - case SOUND_PLAYER_HURT_LONG: - sound_pain_long.play_random(volume_tee*vol, pan); - break; - case SOUND_PLAYER_SPAWN: - sound_spawn.play_random(volume_tee*vol, pan); - break; - case SOUND_PLAYER_CHAIN_LOOP: - sound_hook_loop.play_random(volume_gun*vol, pan); - break; - case SOUND_PLAYER_CHAIN_IMPACT: - sound_hook_attach.play_random(volume_gun*vol, pan); - break; - case SOUND_PLAYER_IMPACT: - sound_body_land.play_random(volume_hit*vol, pan); - break; - case SOUND_PLAYER_IMPACT_NINJA: - sound_ninja_hit.play_random(volume_hit*vol, pan); - break; - case SOUND_PLAYER_DIE: - sound_body_splat.play_random(volume_tee*vol, pan); - break; - case SOUND_PLAYER_SWITCHWEAPON: - sound_weapon_switch.play_random(volume_gun*vol, pan); - break; - case SOUND_PLAYER_EQUIP: - break; - case SOUND_PLAYER_LAND: - sound_body_land.play_random(volume_tee*vol, pan); - break; + // TODO: we need to control the volume of the diffrent sounds + // depening on the category + snd_play_random(soundid, vol, pan); } } } @@ -1257,9 +465,10 @@ void modc_newsnapshot() static void render_projectile(obj_projectile *prev, obj_projectile *current) { - gfx_texture_set(texture_weapon); + gfx_texture_set(data->images[IMAGE_WEAPONS].id); gfx_quads_begin(); - cell_select_ex(numcellsx,numcellsy,weaponprojtexcoord[current->type].x, weaponprojtexcoord[current->type].y, weaponprojtexcoord[current->type].w, weaponprojtexcoord[current->type].h); + + select_sprite(data->weapons[current->type%data->num_weapons].sprite_proj); vec2 vel(current->vx, current->vy); // TODO: interpolare angle aswell @@ -1268,7 +477,7 @@ static void render_projectile(obj_projectile *prev, obj_projectile *current) else gfx_quads_setrotation(0); - vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), snap_intratick()); + vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick()); gfx_quads_draw(pos.x, pos.y,32,32); gfx_quads_setrotation(0); gfx_quads_end(); @@ -1276,163 +485,155 @@ static void render_projectile(obj_projectile *prev, obj_projectile *current) static void render_powerup(obj_powerup *prev, obj_powerup *current) { - //dbg_msg("client", "rendering powerup at %d,%d", current->x, current->y); - - gfx_texture_set(texture_weapon); + gfx_texture_set(data->images[IMAGE_WEAPONS].id); gfx_quads_begin(); float angle = 0.0f; - float sizex = 64.0f; - float sizey = 64.0f; + float size = 64.0f; if (current->type == POWERUP_TYPE_WEAPON) { angle = -0.25f * pi * 2.0f; - cell_select_ex(numcellsx,numcellsy,weapontexcoord[current->subtype].x, weapontexcoord[current->subtype].y, weapontexcoord[current->subtype].w, weapontexcoord[current->subtype].h); - sizex = weaponrenderparams[current->subtype].sizex; - sizey = weaponrenderparams[current->subtype].sizey; + select_sprite(data->weapons[current->subtype%data->num_weapons].sprite_body); + size = data->weapons[current->subtype%data->num_weapons].visual_size; } else - cell_select_ex(numcellsx,numcellsy,poweruptexcoord[current->type].x, poweruptexcoord[current->type].y, poweruptexcoord[current->type].w, poweruptexcoord[current->type].h); - vec2 vel(current->vx, current->vy); + { + const int c[] = { + SPRITE_POWERUP_HEALTH, + SPRITE_POWERUP_ARMOR, + SPRITE_POWERUP_WEAPON, + SPRITE_POWERUP_NINJA, + SPRITE_POWERUP_TIMEFIELD + }; + select_sprite(c[current->type]); + } gfx_quads_setrotation(angle); - // TODO: interpolare angle aswell - /*if(length(vel) > 0.00001f) - gfx_quads_setrotation(get_angle(vel)); - else - gfx_quads_setrotation(0);*/ - vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), snap_intratick()); + vec2 pos = mix(vec2(prev->x, prev->y), vec2(current->x, current->y), client_intratick()); float offset = pos.y/32.0f + pos.x/32.0f; - gfx_quads_draw(pos.x+cosf(client_localtime()*2.0f+offset)*2.5f, pos.y+sinf(client_localtime()*2.0f+offset)*2.5f,sizex * 0.65f,sizey * 0.65f); - gfx_quads_setrotation(0); + pos.x += cosf(client_localtime()*2.0f+offset)*2.5f; + pos.y += sinf(client_localtime()*2.0f+offset)*2.5f; + draw_sprite(pos.x, pos.y, size); gfx_quads_end(); } -float getmeleeangle(vec2 direction, obj_player* prev, obj_player* player) +static void anim_seq_eval(sequence *seq, float time, keyframe *frame) { - vec2 meleedir(0.53, -0.84); - meleedir = normalize(meleedir); - vec2 meleedirattack(0.95, -0.3); - meleedirattack = normalize(meleedirattack); - - if(direction.x < 0) + if(seq->num_frames == 0) { - meleedir.x = -meleedir.x; - meleedirattack.x = -meleedirattack.x; + frame->time = 0; + frame->x = 0; + frame->y = 0; + frame->angle = 0; } - - // 0 -> visualtimeattack go to end pose, (len - visualime) -> go back to normal pose - - float angle = get_angle(meleedir); - if (prev->attackticks) + else if(seq->num_frames == 1) { - float angleattack = get_angle(meleedirattack); - int phase1tick = (player->attacklen - player->attackticks); - if (phase1tick < player->visualtimeattack) + *frame = seq->frames[0]; + } + else + { + //time = max(0.0f, min(1.0f, time / duration)); // TODO: use clamp + keyframe *frame1 = 0; + keyframe *frame2 = 0; + float blend = 0.0f; + + // TODO: make this smarter.. binary search + for (int i = 1; i < seq->num_frames; i++) { - float intratick = snap_intratick(); - float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack); - angle = LERP(angle, angleattack, min(1.0f,max(0.0f,t))); - } - else + if (seq->frames[i-1].time <= time && seq->frames[i].time >= time) + { + frame1 = &seq->frames[i-1]; + frame2 = &seq->frames[i]; + blend = (time - frame1->time) / (frame2->time - frame1->time); + break; + } + } + + if (frame1 && frame2) { - // go back to normal pose - int phase2tick = (player->attacklen - player->visualtimeattack - player->attackticks); - float intratick = snap_intratick(); - float t = ((((float)phase2tick) + intratick)/(float)player->visualtimeattack); - angle = LERP(angleattack, angle, min(1.0f,max(0.0f,t))); + frame->time = time; + frame->x = mix(frame1->x, frame2->x, blend); + frame->y = mix(frame1->y, frame2->y, blend); + frame->angle = mix(frame1->angle, frame2->angle, blend); } } - /*if (prev->attackticks && !player->attackticks) - { - // blend back to normal - float angleattack = get_angle(meleedirattack); - angle = LERP(angleattack, angle, min(1.0f,max(0.0f,snap_intratick()))); - } - else if (player->attackticks) - { - float angleattack = get_angle(meleedirattack); - float intratick = snap_intratick(); - float t = ((((float)player->attackticks) - intratick)/(float)player->attacklen); - angle = LERP(angleattack, angle, min(1.0f,max(0.0f,t))); - }*/ - - return angle; } -float gethammereangle(vec2 direction, obj_player* prev, obj_player* player) +struct animstate { - float t = 0.0f; - if (prev->attackticks) - t = 1.0f - ((((float)player->attackticks) - snap_intratick())/(float)player->attacklen); + keyframe body; + keyframe back_foot; + keyframe front_foot; + keyframe attach; +}; - vec2 pos; - float angle = 0.0f; - hammeranim.evalanim(t,pos,angle); - if(direction.x < 0) - angle = pi -angle;// + ; - //dbg_msg("anim", "Time: %f", t); - return angle; +static void anim_eval(animation *anim, float time, animstate *state) +{ + anim_seq_eval(&anim->body, time, &state->body); + anim_seq_eval(&anim->back_foot, time, &state->back_foot); + anim_seq_eval(&anim->front_foot, time, &state->front_foot); + anim_seq_eval(&anim->attach, time, &state->attach); } -float getninjaangle(vec2 direction, obj_player* prev, obj_player* player) +static void anim_add_keyframe(keyframe *seq, keyframe *added, float amount) { - float t = 0.0f; - if (prev->attackticks) - t = 1.0f - ((((float)player->attackticks) - snap_intratick())/(float)player->attacklen); - - vec2 pos; - float angle = 0.0f; - ninjaanim.evalanim(t,pos,angle); - if(direction.x < 0) - angle = pi -angle;// + ; - //dbg_msg("anim", "Time: %f", t); - return angle; + seq->x += added->x*amount; + seq->y += added->y*amount; + seq->angle += added->angle*amount; } - -float getrecoil(obj_player* prev, obj_player* player) +static void anim_add(animstate *state, animstate *added, float amount) { - // attack = -10 - float recoil = 0.0f; - if (prev->attackticks) - { - float attackrecoil = recoils[player->weapon]; - int phase1tick = (player->attacklen - player->attackticks); - if (phase1tick < player->visualtimeattack) - { - float intratick = snap_intratick(); - float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack); - recoil = LERP(0, attackrecoil, min(1.0f,max(0.0f,t))); - } - else - { - // go back to normal pose - int phase2tick = (player->attacklen - player->visualtimeattack - player->attackticks); - float intratick = snap_intratick(); - float t = ((((float)phase2tick) + intratick)/(float)(player->attacklen - player->visualtimeattack)); - recoil = LERP(attackrecoil, 0.0f, min(1.0f,max(0.0f,t))); - } - } - return recoil; + anim_add_keyframe(&state->body, &added->body, amount); + anim_add_keyframe(&state->back_foot, &added->back_foot, amount); + anim_add_keyframe(&state->front_foot, &added->front_foot, amount); + anim_add_keyframe(&state->attach, &added->attach, amount); +} + +static void anim_eval_add(animstate *state, animation *anim, float time, float amount) +{ + animstate add; + anim_eval(anim, time, &add); + anim_add(state, &add, amount); } static void render_player(obj_player *prev, obj_player *player) { vec2 direction = get_direction(player->angle); float angle = player->angle/256.0f; - vec2 position = mix(vec2(prev->x, prev->y), vec2(player->x, player->y), snap_intratick()); + vec2 position = mix(vec2(prev->x, prev->y), vec2(player->x, player->y), client_intratick()); + bool stationary = player->vx < 1 && player->vx > -1; + bool inair = col_check_point(player->x, player->y+16) == 0; + + // evaluate animation + float walk_time = fmod(position.x, 100.0f)/100.0f; + animstate state; + anim_eval(&data->animations[ANIM_BASE], 0, &state); + + if(inair) + anim_eval_add(&state, &data->animations[ANIM_INAIR], 0, 1.0f); // TODO: some sort of time here + else if(stationary) + anim_eval_add(&state, &data->animations[ANIM_IDLE], 0, 1.0f); // TODO: some sort of time here + else + anim_eval_add(&state, &data->animations[ANIM_WALK], walk_time, 1.0f); + + if (player->weapon == WEAPON_HAMMER) + { + float a = clamp((client_tick()-player->attacktick+client_intratick())/7.5f, 0.0f, 1.0f); + anim_eval_add(&state, &data->animations[ANIM_HAMMER_SWING], a, 1.0f); + } + + // draw hook if(player->hook_active) { - gfx_texture_set(texture_weapon); + gfx_texture_set(data->images[IMAGE_WEAPONS].id); gfx_quads_begin(); //gfx_quads_begin(); vec2 pos = position; - - vec2 hook_pos = mix(vec2(prev->hook_x, prev->hook_y), vec2(player->hook_x, player->hook_y), snap_intratick()); + vec2 hook_pos = mix(vec2(prev->hook_x, prev->hook_y), vec2(player->hook_x, player->hook_y), client_intratick()); float d = distance(pos, hook_pos); vec2 dir = normalize(pos-hook_pos); @@ -1440,11 +641,11 @@ static void render_player(obj_player *prev, obj_player *player) gfx_quads_setrotation(get_angle(dir)+pi); // render head - cell_select_ex(numcellsx,numcellsy, chainheadtexcoord.x,chainheadtexcoord.y, chainheadtexcoord.w, chainheadtexcoord.h); + select_sprite(SPRITE_HOOK_HEAD); gfx_quads_draw(hook_pos.x, hook_pos.y, 24,16); // render chain - cell_select_ex(numcellsx,numcellsy, chaintexcoord.x, chaintexcoord.y, chaintexcoord.w, chaintexcoord.h); + select_sprite(SPRITE_HOOK_CHAIN); for(float f = 24; f < d; f += 24) { vec2 p = hook_pos + dir*f; @@ -1457,131 +658,51 @@ static void render_player(obj_player *prev, obj_player *player) // draw gun { - gfx_texture_set(texture_weapon); + gfx_texture_set(data->images[IMAGE_WEAPONS].id); gfx_quads_begin(); - gfx_quads_setrotation(angle); + gfx_quads_setrotation(state.attach.angle*pi*2+angle); - if (player->modifier & (1 << MODIFIER_TYPE_NINJA)) + // normal weapons + int iw = clamp(player->weapon, 0, NUM_WEAPONS-1); + select_sprite(data->weapons[iw].sprite_body, direction.x < 0 ? SPRITE_FLAG_FLIP_Y : 0); + + vec2 dir = direction; + float recoil = 0.0f; + if (player->weapon == WEAPON_HAMMER) { - float playerangle = angle; - // render NINJA!!! (0.53, 0.84) when idle to -> (0.95, 0.3) at the end of attack + // if attack is under way, bash stuffs if(direction.x < 0) - cell_select_ex_flip_y(numcellsx, numcellsy, modifiertexcoord[MODIFIER_TYPE_NINJA].x, modifiertexcoord[MODIFIER_TYPE_NINJA].y, modifiertexcoord[MODIFIER_TYPE_NINJA].w, modifiertexcoord[MODIFIER_TYPE_NINJA].h); + gfx_quads_setrotation(-pi/2-state.attach.angle*pi*2); else - cell_select_ex(numcellsx, numcellsy, modifiertexcoord[MODIFIER_TYPE_NINJA].x, modifiertexcoord[MODIFIER_TYPE_NINJA].y, modifiertexcoord[MODIFIER_TYPE_NINJA].w, modifiertexcoord[MODIFIER_TYPE_NINJA].h); - - angle = getninjaangle(direction, prev, player);//getmeleeangle(direction, prev, player); - vec2 ninjadir = get_direction((int)(angle * 256.0f)); // TODO: ugly, fix this - gfx_quads_setrotation(angle); - vec2 p = position + vec2(0,modifierrenderparams[MODIFIER_TYPE_NINJA].offsety)+ ninjadir * modifierrenderparams[MODIFIER_TYPE_NINJA].offsetx; - // if attack is active hold it differently and draw speedlines behind us? - gfx_quads_draw(p.x,p.y/*+bob*/,modifierrenderparams[MODIFIER_TYPE_NINJA].sizex, modifierrenderparams[MODIFIER_TYPE_NINJA].sizey); - - if ((player->attacklen - player->attackticks) <= (SERVER_TICK_SPEED / 5)) - { - gfx_quads_setrotation(playerangle); - int ihadoken = rand() % NUMHADOKENS; - cell_select_ex(numcellsx, numcellsy, hadoken[ihadoken].x, hadoken[ihadoken].y, hadoken[ihadoken].w, hadoken[ihadoken].h); - vec2 p = position + vec2(0,hadokenparams[ihadoken].offsety)+ direction * hadokenparams[ihadoken].offsetx; - gfx_quads_draw(p.x,p.y/*+bob*/,hadokenparams[ihadoken].sizex, hadokenparams[ihadoken].sizey); - } + gfx_quads_setrotation(-pi/2+state.attach.angle*pi*2); } else { - // normal weapons - if(direction.x < 0) - cell_select_ex_flip_y(numcellsx, numcellsy, weapontexcoord[player->weapon].x, weapontexcoord[player->weapon].y, weapontexcoord[player->weapon].w, weapontexcoord[player->weapon].h); - else - cell_select_ex(numcellsx, numcellsy, weapontexcoord[player->weapon].x, weapontexcoord[player->weapon].y, weapontexcoord[player->weapon].w, weapontexcoord[player->weapon].h); - - vec2 dir = direction; - float recoil = 0.0f; - if (player->weapon == WEAPON_TYPE_MELEE) - { - // if attack is under way, bash stuffs - //angle = getmeleeangle(direction, prev, player); - angle = gethammereangle(direction, prev, player); - gfx_quads_setrotation(angle); - dir = get_direction((int)(angle * 256.0f)); // TODO: ugly, fix this - } - else - { - recoil = getrecoil(prev, player); - } - - vec2 p = position + vec2(0,weaponrenderparams[player->weapon].offsety) + dir * weaponrenderparams[player->weapon].offsetx - dir * recoil; - gfx_quads_draw(p.x,p.y/*+bob*/,weaponrenderparams[player->weapon].sizex, weaponrenderparams[player->weapon].sizey); - // draw muzzleflare - if (player->weapon == WEAPON_TYPE_GUN || player->weapon == WEAPON_TYPE_SHOTGUN) - { - // check if we're firing stuff - if (true)///prev->attackticks) - { - float alpha = 0.0f; - int phase1tick = (player->attacklen - player->attackticks); - if (phase1tick < (player->visualtimeattack + 3)) - { - float intratick = snap_intratick(); - float t = ((((float)phase1tick) + intratick)/(float)player->visualtimeattack); - alpha = LERP(2.0, 0.0f, min(1.0f,max(0.0f,t))); - } - - if (alpha > 0.0f) - { - float offsety = -muzzleparams[player->weapon].offsety; - int itex = rand() % nummuzzletex[player->weapon]; - if(direction.x < 0) - { - offsety = -offsety; - cell_select_ex_flip_y(numcellsx, numcellsy, muzzletexcoord[player->weapon][itex].x, muzzletexcoord[player->weapon][itex].y, muzzletexcoord[player->weapon][itex].w, muzzletexcoord[player->weapon][itex].h); - } - else - cell_select_ex(numcellsx, numcellsy, muzzletexcoord[player->weapon][itex].x, muzzletexcoord[player->weapon][itex].y, muzzletexcoord[player->weapon][itex].w, muzzletexcoord[player->weapon][itex].h); - - gfx_quads_setcolor(1.0f,1.0f,1.0f,alpha); - vec2 diry(-dir.y,dir.x); - p += dir * muzzleparams[player->weapon].offsetx + diry * offsety; - gfx_quads_draw(p.x,p.y/*+bob*/,muzzleparams[player->weapon].sizex, muzzleparams[player->weapon].sizey); - } - } - } + // TODO: should be an animation + recoil = 0; + float a = (client_tick()-player->attacktick+client_intratick())/5.0f; + if(a < 1) + recoil = sinf(a*pi); } - /*else - { - // minigun - if(direction.x < 0) - cell_select_flip_y(4,4,8,2); - else - cell_select(4,4,8,2); - vec2 p = position + vec2(0,3); - gfx_quads_draw(p.x,p.y,8*8,8*2); - }*/ - - gfx_quads_setrotation(0); + + vec2 p = position + dir*20.0f - dir*recoil*10.0f; + draw_sprite(p.x, p.y, data->weapons[iw].visual_size); + + // TODO: draw muzzleflare gfx_quads_end(); } - - gfx_texture_set(texture_char_default); + gfx_texture_set(data->images[IMAGE_CHAR_DEFAULT].id); gfx_quads_begin(); - float bob = 0; - // draw foots - const float cyclelength = 128.0f; - const float steplength = 26; - const float lift = 4.0f; - bool stationary = player->vx < 1 && player->vx > -1; - bool inair = col_check_point(player->x, player->y+16) == 0; - for(int p = 0; p < 2; p++) { // first pass we draw the outline // second pass we draw the filling - //int v_offset = p?0:5; - int outline = p;// ? 1 : 0; - float offsety = charids[player->clientid % 16] * 2.0f; + int outline = p==0 ? 1 : 0; + int shift = charids[player->clientid%16]; for(int f = 0; f < 2; f++) { @@ -1589,138 +710,41 @@ static void render_player(obj_player *prev, obj_player *player) if(f == 1) { // draw body - float t = fmod(position.x, cyclelength/2)/(cyclelength/2); - bob = -sinf(pow(t,2)*pi) * 3; - cell_select_ex(charnumcellsx,charnumcellsy, body[outline].x,body[outline].y + offsety,body[outline].w,body[outline].h); - //cell_select_ex(16,16, 0,0+v_offset,4,4); - //const float size = 64.0f; - if(stationary || inair) - bob = 0; - gfx_quads_draw(position.x, position.y-5+bob, 4*basesize, 4*basesize); + select_sprite(outline?SPRITE_TEE_BODY_OUTLINE:SPRITE_TEE_BODY, 0, 0, shift*4); + gfx_quads_draw(position.x+state.body.x, position.y+state.body.y, 4*basesize, 4*basesize); // draw eyes if(p == 1) { - //cell_select_ex(16,16, 8,3,1,1); vec2 md = get_direction(player->angle); float mouse_dir_x = md.x; float mouse_dir_y = md.y; // normal - cell_select_ex(charnumcellsx,charnumcellsy, leye.x,leye.y + offsety,leye.w,leye.h); - gfx_quads_draw(position.x-4+mouse_dir_x*4, position.y-8+mouse_dir_y*3+bob, basesize, basesize); - cell_select_ex(charnumcellsx,charnumcellsy, reye.x,reye.y + offsety,reye.w,reye.h); - gfx_quads_draw(position.x+4+mouse_dir_x*4, position.y-8+mouse_dir_y*3+bob, basesize, basesize); + select_sprite(SPRITE_TEE_EYE_NORMAL, 0, 0, shift*4); + gfx_quads_draw(position.x-4+mouse_dir_x*4, position.y-8+mouse_dir_y*3, basesize, basesize); + gfx_quads_draw(position.x+4+mouse_dir_x*4, position.y-8+mouse_dir_y*3, basesize, basesize); } } // draw feet - //cell_select_ex(16,16, 5,2+v_offset, 2,2); - cell_select_ex(charnumcellsx,charnumcellsy, feet[outline].x,feet[outline].y + offsety, feet[outline].w,feet[outline].h); + select_sprite(outline?SPRITE_TEE_FOOT_OUTLINE:SPRITE_TEE_FOOT, 0, 0, shift*4); + + keyframe *foot = f ? &state.front_foot : &state.back_foot; + float w = basesize*2.5f; float h = basesize*1.425f; - if(inair) - { - float r = 0.0f; - if(player->vy < 0.0f) - r = player->vy/3.0f; - else - r = player->vy/15.0f; - - // clamp the rotation - if(r > 0.5f) r = 0.5f; - if(r < -0.5f) r = -0.5f; - - if(player->vx > 0.0f) - r *= -1.0f; - gfx_quads_setrotation(r); - gfx_quads_drawTL(position.x-4+f*7-w/2, position.y+16 - h, w, h); - gfx_quads_setrotation(0); - } - else if(stationary) - { - // stationary - gfx_quads_drawTL(position.x-7+f*14-w/2, position.y+16 - h, w, h); - } - else - { - /* - The walk cycle, 2 parts - - 111 - 1 1 - 2 1 - 2 1 - 2222221 - GROUND GROUND GROUND - */ - - // moving - float tx = position.x+f*(cyclelength/2); - float t = fmod(tx, cyclelength) / cyclelength; - if(player->vx < 0) - t = 1.0f-t; - - float y; - float x = 0; - float r = 0; - float r_back = 1.5f; - - if(t < 0.5f) - { - // stomp down foot (part 1) - float st = t*2; - y = 1.0f-pow(st, 0.5f) + sinf(pow(st,2)*pi)*0.5f; - x = -steplength/2 + st*steplength; - r = r_back*(1-st) + sinf(pow(st,1.5f)*pi*2); - } - else - { - // lift foot up again (part 2) - float st = (t-0.5f)*2; - y = pow(st, 5.0f); - x = steplength/2 - st*steplength; - r = y*r_back; - } - - - if(player->vx > 0) - { - gfx_quads_setrotation(r); - gfx_quads_drawTL(position.x+x-w/2, position.y+16-y*lift - h, w, h); - } - else - { - gfx_quads_setrotation(-r); - gfx_quads_drawTL(position.x-x-w/2, position.y+16-y*lift - h, w, h); - } - gfx_quads_setrotation(0); - } - + + gfx_quads_setrotation(foot->angle); + gfx_quads_draw(position.x+foot->x, position.y+foot->y, w, h); } } gfx_quads_end(); - - } -static player_input oldinput; -static bool bfirst = true; void modc_render() { - if (bfirst) - { - bfirst = false; - oldinput.activeweapon = 0; - oldinput.angle = 0; - oldinput.blink = 0; - oldinput.fire = 0; - oldinput.hook = 0; - oldinput.jump = 0; - oldinput.left = 0; - oldinput.right = 0; - } // fetch new input { int x, y; @@ -1734,31 +758,27 @@ void modc_render() // snap input { player_input input; - input.left = inp_key_pressed(config.key_move_left); - input.right = inp_key_pressed(config.key_move_right); + mem_zero(&input, sizeof(input)); + float a = atan((float)mouse_pos.y/(float)mouse_pos.x); if(mouse_pos.x < 0) a = a+pi; + input.angle = (int)(a*256.0f); + input.left = inp_key_pressed(config.key_move_left); + input.right = inp_key_pressed(config.key_move_right); input.jump = inp_key_pressed(config.key_jump); - - input.fire = inp_key_pressed(config.key_fire);// | (oldinput.fire << 16); - //oldinput.fire = input.fire & 0x0000ffff; - + input.fire = inp_key_pressed(config.key_fire); input.hook = inp_key_pressed(config.key_hook); input.blink = inp_key_pressed('S'); // Weapon switching - input.activeweapon = inp_key_pressed('1') ? 0x80000000 : 0; - if (!input.activeweapon) - input.activeweapon = inp_key_pressed('2') ? 0x80000000 | 1 : 0; - if (!input.activeweapon) - input.activeweapon = inp_key_pressed('3') ? 0x80000000 | 2 : 0; - if (!input.activeweapon) - input.activeweapon = inp_key_pressed('4') ? 0x80000000 | 3 : 0; - /*if (!input.activeweapon) - input.activeweapon = inp_key_pressed('5') ? 0x80000000 | 4 : 0;*/ + input.activeweapon = -1; + input.activeweapon = inp_key_pressed('1') ? 0 : input.activeweapon; + input.activeweapon = inp_key_pressed('2') ? 1 : input.activeweapon; + input.activeweapon = inp_key_pressed('3') ? 2 : input.activeweapon; + input.activeweapon = inp_key_pressed('4') ? 3 : input.activeweapon; snap_input(&input, sizeof(input)); } @@ -1767,7 +787,6 @@ void modc_render() { // 1. fetch local player // 2. set him to the center - int num = snap_num_items(SNAP_CURRENT); for(int i = 0; i < num; i++) { @@ -1784,7 +803,7 @@ void modc_render() void *p = snap_find_item(SNAP_PREV, item.type, item.id); if(p) - local_player_pos = mix(vec2(((obj_player *)p)->x, ((obj_player *)p)->y), local_player_pos, snap_intratick()); + local_player_pos = mix(vec2(((obj_player *)p)->x, ((obj_player *)p)->y), local_player_pos, client_intratick()); break; } } @@ -1816,9 +835,9 @@ void modc_render() // draw background gfx_clear(0.65f,0.78f,0.9f); - - { + // draw the sun + { vec2 pos(local_player_pos.x*0.5f, local_player_pos.y*0.5f); gfx_texture_set(-1); @@ -1833,8 +852,6 @@ void modc_render() vec2 dir0(sinf((a-size)*pi*2.0f), cosf((a-size)*pi*2.0f)); vec2 dir1(sinf((a+size)*pi*2.0f), cosf((a+size)*pi*2.0f)); - //gfx_quads_draw_freeform(0,0, -100,0, -100,-100, 0,-100); - gfx_quads_setcolorvertex(0, 1.0f,1.0f,1.0f,0.025f); gfx_quads_setcolorvertex(1, 1.0f,1.0f,1.0f,0.025f); gfx_quads_setcolorvertex(2, 1.0f,1.0f,1.0f,0.0f); @@ -1849,7 +866,7 @@ void modc_render() gfx_quads_end(); gfx_blend_normal(); - gfx_texture_set(texture_sun); + gfx_texture_set(data->images[IMAGE_SUN].id); gfx_quads_begin(); gfx_quads_draw(pos.x, pos.y, 256, 256); gfx_quads_end(); @@ -1857,9 +874,6 @@ void modc_render() // render map tilemap_render(32.0f, 0); -#ifdef _DEBUG - float speed = 0.0f; -#endif // render items int num = snap_num_items(SNAP_CURRENT); for(int i = 0; i < num; i++) @@ -1871,18 +885,7 @@ void modc_render() { void *prev = snap_find_item(SNAP_PREV, item.type, item.id); if(prev) - { render_player((obj_player *)prev, (obj_player *)data); -/*#ifdef _DEBUG - { - obj_player *p = (obj_player *)prev; - obj_player *c = (obj_player *)data; - vec2 positionold = vec2(p->x, p->y); - vec2 poscur = vec2(c->x, c->y); - speed = distance(positionold,poscur); - } -#endif*/ - } } else if(item.type == OBJTYPE_PROJECTILE) { @@ -1907,68 +910,58 @@ void modc_render() // render health mods healthmods.render(); - // render cursor - // FIXME CURSOR!!! - if(local_player) { - gfx_texture_set(texture_weapon); + gfx_texture_set(data->images[IMAGE_WEAPONS].id); gfx_quads_begin(); - if (local_player->modifier & (1 << MODIFIER_TYPE_NINJA)) - cell_select_ex(numcellsx,numcellsy, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].x, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].y, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].w, modifiertexcoordcursor[MODIFIER_TYPE_NINJA].h); - else - cell_select_ex(numcellsx,numcellsy, weapontexcoordcursor[local_player->weapon].x, weapontexcoordcursor[local_player->weapon].y, weapontexcoordcursor[local_player->weapon].w, weapontexcoordcursor[local_player->weapon].h); + + // render cursor + select_sprite(data->weapons[local_player->weapon%data->num_weapons].sprite_cursor); float cursorsize = 64; - gfx_quads_draw(local_player_pos.x+mouse_pos.x, local_player_pos.y+mouse_pos.y,cursorsize,cursorsize); - + draw_sprite(local_player_pos.x+mouse_pos.x, local_player_pos.y+mouse_pos.y, cursorsize); // render ammo count // render gui stuff gfx_quads_end(); gfx_quads_begin(); gfx_mapscreen(0,0,400,300); - cell_select_ex(numcellsx,numcellsy, weaponprojtexcoord[local_player->weapon].x, weaponprojtexcoord[local_player->weapon].y, weaponprojtexcoord[local_player->weapon].w, weaponprojtexcoord[local_player->weapon].h); + select_sprite(data->weapons[local_player->weapon%data->num_weapons].sprite_proj); for (int i = 0; i < local_player->ammocount; i++) - { gfx_quads_drawTL(10+i*12,34,10,10); - } gfx_quads_end(); - gfx_texture_set(texture_game); + gfx_texture_set(data->images[IMAGE_GAME].id); gfx_quads_begin(); int h = 0; - cell_select_ex(32,16, 0,0, 4,4); + + // render health + select_sprite(SPRITE_HEALTH_FULL); for(; h < local_player->health; h++) gfx_quads_drawTL(10+h*12,10,10,10); - cell_select_ex(32,16, 5,0, 4,4); + select_sprite(SPRITE_HEALTH_EMPTY); for(; h < 10; h++) gfx_quads_drawTL(10+h*12,10,10,10); + // render armor meter h = 0; - cell_select_ex(32,16, 0,5, 4,4); + select_sprite(SPRITE_ARMOR_FULL); for(; h < local_player->armor; h++) gfx_quads_drawTL(10+h*12,22,10,10); - cell_select_ex(32,16, 5,5, 4,4); + select_sprite(SPRITE_ARMOR_EMPTY); for(; h < 10; h++) gfx_quads_drawTL(10+h*12,22,10,10); gfx_quads_end(); - - // render speed -/*#ifdef _DEBUG - gfx_texture_set(font_texture); - char text[256]; - sprintf(text,"speed: %f",speed); - gfx_quads_text(300,20,10,text); -#endif*/ } + // render gui stuff gfx_mapscreen(0,0,400,300); + // render score board if(inp_key_pressed(baselib::input::tab)) { - gfx_texture_set(font_texture); + gfx_texture_set(-1); gfx_quads_text(10, 50, 8, "Score Board"); int num = snap_num_items(SNAP_CURRENT); @@ -1984,8 +977,8 @@ void modc_render() if(player) { char buf[128]; - char name[32]; - snap_decode_string(player->name, name, 32); + char name[32] = "tjo"; + //snap_decode_string(player->name, name, 32); sprintf(buf, "%4d %s", player->score, name); gfx_quads_text(10, 50 + 10 * row, 8, buf); row++; diff --git a/src/game/client/mapres_tilemap.cpp b/src/game/client/mapres_tilemap.cpp index 36302d0aa..52f2a9b1c 100644 --- a/src/game/client/mapres_tilemap.cpp +++ b/src/game/client/mapres_tilemap.cpp @@ -33,7 +33,8 @@ void tilemap_render(float scale, int fg) gfx_quads_begin(); int c = 0; - float frac = (1.0f/1024.0f); //2.0f; + float frac = (1.0f/1024.0f);//2.0f; //2.0f; + const float s = 1.0f; for(int y = 0; y < tmap->height; y++) for(int x = 0; x < tmap->width; x++, c++) { @@ -41,13 +42,14 @@ void tilemap_render(float scale, int fg) if(d) { gfx_quads_setsubset( - (d%16)/16.0f+frac, - (d/16)/16.0f+frac, - (d%16)/16.0f+1.0f/16.0f-frac, - (d/16)/16.0f+1.0f/16.0f-frac); + (d%16)/16.0f*s+frac, + (d/16)/16.0f*s+frac, + ((d%16)/16.0f+1.0f/16.0f)*s-frac, + ((d/16)/16.0f+1.0f/16.0f)*s-frac); gfx_quads_drawTL(x*scale, y*scale, scale, scale); } } + gfx_quads_end(); } } diff --git a/src/game/client/menu.cpp b/src/game/client/menu.cpp index 128965851..c8c41e4b2 100644 --- a/src/game/client/menu.cpp +++ b/src/game/client/menu.cpp @@ -374,13 +374,14 @@ void draw_single_part_button(void *id, const char *text, int checked, float x, f else tileset = tileset_regular; - draw_part((gui_parts_enum)(int)(int *)extra, tileset, x, y, w, h); + + draw_part((gui_parts_enum)((char*)extra-(char*)0), tileset, x, y, w, h); } void draw_menu_button(void *id, const char *text, int checked, float x, float y, float w, float h, void *extra) { gui_composite_box_enum box_style; - if ((int)extra) + if ((int)((char*)extra-(char*)0)) box_style = screen_info_box; else box_style = screen_list_box; @@ -401,7 +402,7 @@ void draw_teewars_button(void *id, const char *text, int checked, float x, float else tileset = tileset_regular; - if ((int)(int *)extra == 1) + if ((int)((char*)extra-(char*)0) == 1) tileset = tileset_inactive; draw_box(button_big_box, tileset, x, y, w, h); @@ -486,7 +487,7 @@ int ui_do_combo_box(void *id, float x, float y, float w, char *lines, int line_c float line_height = 36.0f; int inside = (ui_active_item() == id) ? ui_mouse_inside(x, y, w, line_count * line_height) : ui_mouse_inside(x, y, w, line_height); - int hover_index = (ui_mouse_y() - y) / line_height; + int hover_index = (int)((ui_mouse_y() - y) / line_height); if (ui_active_item() == id) { diff --git a/src/game/game.h b/src/game/game.h index 2696b74df..a14718879 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -1,16 +1,9 @@ #include -#include +#include #include #include "../engine/interface.h" #include "mapres_col.h" -// Don't tweak :) -const float pi = 3.14159265358979f; - -#define LERP(a,b,t) (a + (b-a) * t) -#define min(a, b) ( a > b ? b : a) -#define max(a, b) ( a > b ? a : b) - inline baselib::vec2 get_direction(int angle) { float a = angle/256.0f; @@ -21,7 +14,7 @@ inline float get_angle(baselib::vec2 dir) { float a = atan(dir.y/dir.x); if(dir.x < 0) - a = a+pi; + a = a+baselib::pi; return a; } @@ -76,7 +69,8 @@ struct ev_sound struct ev_healthmod { - int x, y; + int x, y; // could perhaps be an client id + int vx, vy; // should be an angle instead int amount; }; @@ -84,20 +78,19 @@ struct obj_projectile { int type; int x, y; - int vx, vy; + int vx, vy; // should be an angle instead }; struct obj_powerup { - int type; - int subtype; int x, y; - int vx, vy; + int type; // why do we need two types? + int subtype; }; struct obj_player { - int name[8]; + //int name[8]; int local; int clientid; @@ -113,12 +106,10 @@ struct obj_player // current active weapon int weapon; // current active modifier - int modifier; + //int modifier; - // num attack ticks left of current attck - int attackticks; - int attacklen; - int visualtimeattack; + // num attack ticks left of current attack + int attacktick; int score; int emote; @@ -183,49 +174,3 @@ enum ITEM_ARMOR_10=0x00030010, ITEM_NINJA=0x00040001, }; - -// sound categories and stuff -enum -{ - SOUND_FIRE_GUN = 0, - SOUND_FIRE_SHOTGUN, - SOUND_FIRE_ROCKET, - SOUND_FIRE_MELEE, - SOUND_FIRE_NINJA, - - - // impacts with world - SOUND_IMPACT_PROJECTILE_GUN, - SOUND_IMPACT_PROJECTILE_SHOTGUN, - SOUND_IMPACT_PROJECTILE_ROCKET, - - - // chain ? - - // Player movement - SOUND_PLAYER_JUMP, - SOUND_PLAYER_HURT_SHORT, - SOUND_PLAYER_HURT_LONG, - SOUND_PLAYER_SPAWN, - SOUND_PLAYER_CHAIN_LOOP, - SOUND_PLAYER_CHAIN_IMPACT, - SOUND_PLAYER_IMPACT, - SOUND_PLAYER_IMPACT_NINJA, - SOUND_PLAYER_DIE, - SOUND_PLAYER_SWITCHWEAPON, - SOUND_PLAYER_EQUIP, - SOUND_PLAYER_LAND, - - SOUND_NUMSOUNDS, - - - // extra defs (for future?) - SOUND_EQUIP_GUN = SOUND_PLAYER_EQUIP, - SOUND_EQUIP_ROCKET = SOUND_PLAYER_EQUIP, - SOUND_EQUIP_SHOTGUN = SOUND_PLAYER_EQUIP, - SOUND_EQUIP_MELEE = SOUND_PLAYER_EQUIP, - - SOUND_LOOPFLAG_STARTLOOP = 0x80000000, - SOUND_LOOPFLAG_STOPLOOP = 0x40000000, - SOUND_MASK = ~(SOUND_LOOPFLAG_STARTLOOP | SOUND_LOOPFLAG_STOPLOOP), -}; diff --git a/src/game/game_variables.h b/src/game/game_variables.h index bd94106a8..4414d5f2b 100644 --- a/src/game/game_variables.h +++ b/src/game/game_variables.h @@ -1,8 +1,7 @@ -MACRO_CONFIG_INT(screen_width, 800, 0, 0) -MACRO_CONFIG_INT(screen_height, 600, 0, 0) -MACRO_CONFIG_STR(player_name, 32, "nameless tee") MACRO_CONFIG_INT(key_move_left, 65, 32, 512) MACRO_CONFIG_INT(key_move_right, 68, 32, 512) MACRO_CONFIG_INT(key_jump, 32, 32, 512) MACRO_CONFIG_INT(key_fire, 384, 32, 512) MACRO_CONFIG_INT(key_hook, 385, 32, 512) +//MACRO_CONFIG_INT(key_fire, 'E', 32, 512) +//MACRO_CONFIG_INT(key_hook, 'Q', 32, 512) diff --git a/src/game/mapres_col.cpp b/src/game/mapres_col.cpp index 29215d7d9..d2f9d0c69 100644 --- a/src/game/mapres_col.cpp +++ b/src/game/mapres_col.cpp @@ -1,8 +1,12 @@ #include +#include +#include #include "../engine/interface.h" #include "mapres_col.h" #include "mapres.h" +using namespace baselib; + /* Simple collision rutines! */ @@ -42,3 +46,24 @@ int col_check_point(int x, int y) return col.data[ny*col.w+nx]; } + +// TODO: rewrite this smarter! +bool col_intersect_line(vec2 pos0, vec2 pos1, vec2 *out) +{ + float d = distance(pos0, pos1); + + for(float f = 0; f < d; f++) + { + float a = f/d; + vec2 pos = mix(pos0, pos1, a); + if(col_check_point((int)pos.x, (int)pos.y)) + { + if(out) + *out = pos; + return true; + } + } + if(out) + *out = pos1; + return false; +} diff --git a/src/game/mapres_col.h b/src/game/mapres_col.h index 2afad4390..83690577c 100644 --- a/src/game/mapres_col.h +++ b/src/game/mapres_col.h @@ -1,3 +1,4 @@ +#include struct mapres_collision { @@ -8,3 +9,4 @@ struct mapres_collision int col_init(int dividor); int col_check_point(int x, int y); +bool col_intersect_line(baselib::vec2 pos0, baselib::vec2 pos1, baselib::vec2 *out); diff --git a/src/game/server/game_server.cpp b/src/game/server/game_server.cpp index df17169c1..88cbb471c 100644 --- a/src/game/server/game_server.cpp +++ b/src/game/server/game_server.cpp @@ -1,9 +1,13 @@ #include #include #include "../game.h" +#include "data.h" using namespace baselib; +// --------- +const bool debug_bots = false; + // --------- PHYSICS TWEAK! -------- const float ground_control_speed = 7.0f; const float ground_control_accel = 2.0f; @@ -25,6 +29,30 @@ void create_smoke(vec2 p); void create_sound(vec2 pos, int sound, int loopflags = 0); class player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, class entity* notthis = 0); +template +T saturated_add(T min, T max, T current, T modifier) +{ + if(modifier < 0) + { + if(current < min) + return current; + current += modifier; + if(current < min) + current = min; + return current; + } + else + { + if(current > max) + return current; + current += modifier; + if(current > max) + current = max; + return current; + } +} + + // TODO: rewrite this smarter! void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity) { @@ -91,27 +119,6 @@ void move_box(vec2 *inout_pos, vec2 *inout_vel, vec2 size, float elasticity) *inout_vel = vel; } -// TODO: rewrite this smarter! -bool intersect_line(vec2 pos0, vec2 pos1, vec2 *out) -{ - float d = distance(pos0, pos1); - - for(float f = 0; f < d; f++) - { - float a = f/d; - vec2 pos = mix(pos0, pos1, a); - if(col_check_point(pos)) - { - if(out) - *out = pos; - return true; - } - } - if(out) - *out = pos1; - return false; -} - // class event_handler { @@ -128,7 +135,7 @@ class event_handler public: event_handler() { - num_events = 0; + clear(); } void *create(int type, int size) @@ -159,27 +166,6 @@ public: }; static event_handler events; -/* -template -class pool -{ - struct element - { - int next_free; - T data; - }; - - element elements[SIZE]; - int first_free; -public: - pool() - { - first_free = 0; - for(int i = 0; i < SIZE; i++) - elements[i].next_free = i+1; - elements[SIZE-1].next_free = -1; - } -};*/ // a basic entity class entity @@ -190,12 +176,15 @@ private: entity *prev_entity; entity *next_entity; int index; + static int current_id; +protected: + int id; public: - vec2 pos; float proximity_radius; unsigned flags; int objtype; + vec2 pos; enum { @@ -208,6 +197,9 @@ public: pos = vec2(0,0); flags = 0; proximity_radius = 0; + + current_id++; + id = current_id; } virtual ~entity() @@ -216,35 +208,14 @@ public: virtual void destroy() { delete this; } virtual void tick() {} + virtual void tick_defered() {} + virtual void snap(int snapping_client) {} virtual bool take_damage(vec2 force, int dmg, int from) { return true; } }; -class powerup : public entity -{ -public: - static const int phys_size = 14; - enum - { - POWERUP_FLAG_HOOKABLE = 1 << 0, - }; - vec2 vel; - class player* playerhooked; - int type; - int id; - int subtype; // weapon type for instance? - int numitems; // number off powerup items - int flags; - int spawntick; - powerup(int _type, int _subtype = 0, int _numitems = 0, int _flags = 0); - - static void spawnrandom(int _lifespan); - - void tick(); - - void snap(int snapping_client); -}; +int entity::current_id = 1; // game world. handles all entities class game_world @@ -283,6 +254,7 @@ public: if (ent->objtype != types[i]) continue; + // TODO: this seams like it could be done several times unnessesary if(distance(ent->pos, pos) < radius+ent->proximity_radius) { ents[num] = ent; @@ -309,9 +281,6 @@ public: void destroy_entity(entity *ent) { ent->flags |= entity::FLAG_DESTROY; - // call destroy - //remove_entity(ent); - //ent->destroy(); } void remove_entity(entity *ent) @@ -337,6 +306,9 @@ public: // update all objects for(entity *ent = first_entity; ent; ent = ent->next_entity) ent->tick(); + + for(entity *ent = first_entity; ent; ent = ent->next_entity) + ent->tick_defered(); // destroy objects marked for destruction entity *ent = first_entity; @@ -355,6 +327,21 @@ public: static game_world world; +// TODO: move to seperate file +class powerup : public entity +{ +public: + static const int phys_size = 14; + + int type; + int subtype; // weapon type for instance? + int spawntick; + powerup(int _type, int _subtype = 0); + + virtual void tick(); + virtual void snap(int snapping_client); +}; + // projectile entity class projectile : public entity { @@ -363,10 +350,10 @@ public: { PROJECTILE_FLAGS_EXPLODE = 1 << 0, }; + vec2 vel; - entity* powner; + entity *powner; // this is nasty, could be removed when client quits int lifespan; - int id; int owner; int type; int flags; @@ -374,11 +361,9 @@ public: int sound_impact; float force; - projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, int damage, int flags = 0, float force = 0.0f, int sound_impact = -1) : - entity(OBJTYPE_PROJECTILE) + projectile(int type, int owner, vec2 pos, vec2 vel, int span, entity* powner, int damage, int flags = 0, float force = 0.0f, int sound_impact = -1) + : entity(OBJTYPE_PROJECTILE) { - static int current_id = 0; - this->id = current_id++; this->type = type; this->pos = pos; this->vel = vel; @@ -398,20 +383,19 @@ public: vel.y += 0.25f; pos += vel; lifespan--; + // check player intersection as well - entity* targetplayer = (entity*)intersect_player(oldpos, pos, oldpos, powner); + entity *targetplayer = (entity*)intersect_player(oldpos, pos, oldpos, powner); if(targetplayer || lifespan < 0 || col_check_point((int)pos.x, (int)pos.y)) { if (lifespan >= 0) create_sound(pos, sound_impact); + if (flags & PROJECTILE_FLAGS_EXPLODE) - { create_explosion(oldpos, owner); - } else if (targetplayer) - { targetplayer->take_damage(normalize(vel) * force, damage, owner); - } + world.destroy_entity(this); } } @@ -421,639 +405,85 @@ public: obj_projectile *proj = (obj_projectile *)snap_new_item(OBJTYPE_PROJECTILE, id, sizeof(obj_projectile)); proj->x = (int)pos.x; proj->y = (int)pos.y; - proj->vx = (int)vel.x; + proj->vx = (int)vel.x; // TODO: should be an angle proj->vy = (int)vel.y; proj->type = type; } }; // player entity +// TODO: move to separate file class player : public entity { public: static const int phys_size = 28; - enum + + enum // what are these? { - WEAPON_NEEDRELOAD = 1 << 0, - WEAPON_DROPONUNEQUIP = 1 << 1, - WEAPON_DRAWSAMMO = 1 << 2, - WEAPON_HASSECONDARY = 1 << 3, - WEAPON_ISACTIVE = 1 << 4, // has the item - WEAPON_AUTOFIRE = 1 << 5, - WEAPON_PROJECTILETYPE_GUN = 0, WEAPON_PROJECTILETYPE_ROCKET = 1, WEAPON_PROJECTILETYPE_SHOTGUN = 2, - - // Gun - - - // modifiers - MODIFIER_HASACTIVATIONS = 1 << 0, - MODIFIER_TIMELIMITED = 1 << 1, - MODIFIER_ISACTIVE = 1 << 2, - MODIFIER_NEEDSACTIVATION = 1 << 3, - - MODIFIER_RETURNFLAGS_OVERRIDEWEAPON = 1 << 0, - MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY = 1 << 1, - MODIFIER_RETURNFLAGS_OVERRIDEPOSITION = 1 << 2, - MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY = 1 << 3, }; - class weapon + + // weapon info + struct weaponstat { - public: - entity* hitobjects[10]; - int numobjectshit; // for melee, so we don't hit the same object more than once per bash - int weapontype; - int equiptime; - int unequiptime; - int numammo; - int magsize; - int nummagazines; - int flags; - int firetime; - int reloadtime; - int projectileclass; - int damage; - int sound_fire; - int sound_equip; - int sound_impact; - int sound_impact_projectile; - int visualtimeattack; - float projectilevel; - float projectilespan; - float reach; // for melee - float force; - float recoilforce; - float projoffsety; - float projoffsetx; - - weapon() - { - weapontype = 0; - numammo = 0; - flags = 0; - reloadtime = 0; - projectileclass = 0; - numobjectshit = 0; - reach = 0.0f; - force = 5.0f; - damage = 1; - sound_fire = -1; - sound_equip = -1; - sound_impact = -1; - sound_impact_projectile = -1, - visualtimeattack = 3; - recoilforce = 0.0f; - projoffsety = 0.0f; - projoffsetx = 0.0f; - } - - void setgun(int ammo = 10) - { - weapontype = WEAPON_TYPE_GUN; - flags = 0;//WEAPON_DRAWSAMMO; - numammo = ammo; - projectileclass = WEAPON_PROJECTILETYPE_GUN; - firetime = SERVER_TICK_SPEED/10; - magsize = 0; - projectilevel = 30.0f; - projectilespan = 50.0f * 1.0f; - sound_fire = SOUND_FIRE_GUN; - sound_equip = SOUND_EQUIP_GUN; - sound_impact_projectile = SOUND_IMPACT_PROJECTILE_GUN; - projoffsety = -10.0f; - projoffsetx = 24.0f; - } - - void setrocket(int ammo = 10) - { - weapontype = WEAPON_TYPE_ROCKET; - flags = WEAPON_DRAWSAMMO; - numammo = ammo; - projectileclass = WEAPON_PROJECTILETYPE_ROCKET; - projectilevel = 15.0f; - projectilespan = 50.0f * 5.0f; - firetime = SERVER_TICK_SPEED * 4/5; - magsize = 0; - recoilforce = 5.0f; - sound_fire = SOUND_FIRE_ROCKET; - sound_equip = SOUND_EQUIP_ROCKET; - sound_impact_projectile = SOUND_IMPACT_PROJECTILE_ROCKET; - projoffsety = -17.0f; - projoffsetx = 24.0f; - } - - /*void setsniper(int ammo = 10) - { - weapontype = WEAPON_TYPE_SNIPER; - flags = WEAPON_DRAWSAMMO | WEAPON_HASSECONDARY | WEAPON_NEEDRELOAD; - numammo = ammo; - projectileclass = WEAPON_PROJECTILETYPE_SNIPER; - projectilevel = 30.0f; - projectilespan = 50.0f * 5.0f; - firetime = SERVER_TICK_SPEED; - reloadtime = SERVER_TICK_SPEED/2; - magsize = 2; - recoilforce = 20.0f; - }*/ - - void setshotgun(int ammo = 10) - { - weapontype = WEAPON_TYPE_SHOTGUN; - flags = WEAPON_DRAWSAMMO | WEAPON_NEEDRELOAD; - numammo = ammo; - projectileclass = WEAPON_PROJECTILETYPE_SHOTGUN; - projectilevel = 30.0f; - projectilespan = 50.0f * 5.0f; - firetime = SERVER_TICK_SPEED/2; - reloadtime = SERVER_TICK_SPEED/2; - magsize = 2; - damage = 3; - recoilforce = 5.0f; - sound_fire = SOUND_FIRE_SHOTGUN; - sound_equip = SOUND_EQUIP_SHOTGUN; - sound_impact_projectile = SOUND_IMPACT_PROJECTILE_SHOTGUN; - projoffsety = -17.0f; - projoffsetx = 24.0f; - } - - void setmelee(int ammo = 10) - { - weapontype = WEAPON_TYPE_MELEE; - flags = 0;//WEAPON_AUTOFIRE; - numammo = ammo; - projectileclass = -1; - firetime = SERVER_TICK_SPEED/5; - reloadtime = 0; - magsize = 2; - numobjectshit = 0; - reach = 15.0f; - damage = 1; - sound_fire = SOUND_FIRE_MELEE; - sound_equip = SOUND_EQUIP_MELEE; - sound_impact = SOUND_PLAYER_IMPACT; - } - - void settype() - { - switch(weapontype) - { - case WEAPON_TYPE_GUN: - { - setgun(); - break; - } - case WEAPON_TYPE_ROCKET: - { - setrocket(); - break; - } - /*case WEAPON_TYPE_SNIPER: - { - setsniper(); - break; - }*/ - case WEAPON_TYPE_SHOTGUN: - { - setshotgun(); - break; - } - case WEAPON_TYPE_MELEE: - { - setmelee(); - break; - } - default: - break; - } - } - - int activate(player* player) - { - // create sound event for fire - int projectileflags = 0; - create_sound(player->pos, sound_fire); - - switch (weapontype) - { - case WEAPON_TYPE_ROCKET: - projectileflags |= projectile::PROJECTILE_FLAGS_EXPLODE; - case WEAPON_TYPE_GUN: - //case WEAPON_TYPE_SNIPER: - case WEAPON_TYPE_SHOTGUN: - { - if (flags & WEAPON_DRAWSAMMO) - numammo--; - // Create projectile - new projectile(projectileclass, - player->client_id, - player->pos+vec2(0,projoffsety)+player->direction*projoffsetx, - player->direction*projectilevel, - (int)projectilespan, - player, - damage, - projectileflags, - force, - sound_impact_projectile); - // recoil force if any - if (recoilforce > 0.0f) - { - vec2 dir(player->direction.x,0.5); - if (dir.x == 0.0f) - dir.x = 0.5f; - else - dir = normalize(dir); - player->vel -= dir * recoilforce; - } - return firetime; - } - case WEAPON_TYPE_MELEE: - { - // Start bash sequence - numobjectshit = 0; - return firetime; - } - default: - return 0; - } - } - - void update(player* owner, int fire_timeout) - { - switch(weapontype) - { - case WEAPON_TYPE_MELEE: - { - // No more melee - if (fire_timeout <= 0) - return; - - // only one that needs update (for now) - // do selection for the weapon and bash anything in it - // check if we hit anything along the way - int type = OBJTYPE_PLAYER; - entity *ents[64]; - vec2 dir = owner->pos + owner->direction * reach; - float radius = length(dir * 0.5f); - vec2 center = owner->pos + dir * 0.5f; - int num = world.find_entities(center, radius, ents, 64, &type, 1); - - for (int i = 0; i < num; i++) - { - // Check if entity is a player - if (ents[i] == owner) - continue; - // make sure we haven't hit this object before - bool balreadyhit = false; - for (int j = 0; j < numobjectshit; j++) - { - if (hitobjects[j] == ents[i]) - balreadyhit = true; - } - if (balreadyhit) - continue; - - // check so we are sufficiently close - if (distance(ents[i]->pos, owner->pos) > (owner->phys_size * 2.0f)) - continue; - - // hit a player, give him damage and stuffs... - // create sound for bash - create_sound(ents[i]->pos, sound_impact); - - // set his velocity to fast upward (for now) - create_smoke(ents[i]->pos); - hitobjects[numobjectshit++] = ents[i]; - ents[i]->take_damage(vec2(0,10.0f), damage, owner->client_id); - player* target = (player*)ents[i]; - vec2 dir; - if (length(target->pos - owner->pos) > 0.0f) - dir = normalize(target->pos - owner->pos); - else - dir = vec2(0,-1); - target->vel += dir * 10.0f + vec2(0,-10.0f); - } - break; - } - default: - break; - } - } - }; - - class modifier - { - public: - vec2 activationdir; - entity* hitobjects[10]; - int numobjectshit; - float velocity; - int modifiertype; - int duration; - int numactivations; - int activationtime; - int cooldown; - int movetime; - int visualtimeattack; - int currentactivation; - int currentmovetime; - int currentcooldown; - int damage; - int flags; - int sound_impact; - int sound_activate; - - modifier() - { - modifiertype = 0; - duration = 0; - numobjectshit = 0; - numactivations = 0; - activationtime = 0; - cooldown = 0; - movetime = 0; - currentactivation = 0; - currentmovetime = 0; - currentcooldown =0; - damage = 0; - flags = 0; - activationdir = vec2(0.0f, 1.0f); - velocity = 0.0f; - visualtimeattack = 0; - sound_impact = -1; - } - - void setninja() - { - modifiertype = MODIFIER_TYPE_NINJA; - duration = SERVER_TICK_SPEED * 15; - numactivations = -1; - movetime = SERVER_TICK_SPEED / 5; - activationtime = SERVER_TICK_SPEED / 2; - cooldown = SERVER_TICK_SPEED; - currentactivation = 0; - currentmovetime = 0; - numobjectshit = 0; - damage = 3; - flags = MODIFIER_TIMELIMITED | MODIFIER_NEEDSACTIVATION; - velocity = 50.0f; - visualtimeattack = 3; - sound_impact = SOUND_PLAYER_IMPACT_NINJA; - sound_activate = SOUND_FIRE_NINJA; - } - - void settimefield() - { - modifiertype = MODIFIER_TYPE_TIMEFIELD; - duration = SERVER_TICK_SPEED * 10; - numactivations = -1; - activationtime = SERVER_TICK_SPEED; - numobjectshit = 0; - currentactivation = 0; - flags = MODIFIER_TIMELIMITED; - velocity = 0.0f; - } - - void settype() - { - switch (modifiertype) - { - case MODIFIER_TYPE_NINJA: - { - setninja(); - break; - } - case MODIFIER_TYPE_TIMEFIELD: - { - settimefield(); - break; - } - default: - break; - } - } - - int updateninja(player* player) - { - if (currentactivation <= 0) - return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; - currentactivation--; - currentmovetime--; - - if (currentmovetime == 0) - { - // reset player velocity - player->vel *= 0.2f; - //return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; - } - - if (currentmovetime > 0) - { - // Set player velocity - player->vel = activationdir * velocity; - vec2 oldpos = player->pos; - move_box(&player->pos, &player->vel, vec2(player->phys_size, player->phys_size), 0.0f); - // reset velocity so the client doesn't predict stuff - player->vel = vec2(0.0f,0.0f); - if ((currentmovetime % 2) == 0) - { - create_smoke(player->pos); - } - - // check if we hit anything along the way - { - int type = OBJTYPE_PLAYER; - entity *ents[64]; - vec2 dir = player->pos - oldpos; - float radius = length(dir * 0.5f); - vec2 center = oldpos + dir * 0.5f; - int num = world.find_entities(center, radius, ents, 64, &type, 1); - - for (int i = 0; i < num; i++) - { - // Check if entity is a player - if (ents[i] == player) - continue; - // make sure we haven't hit this object before - bool balreadyhit = false; - for (int j = 0; j < numobjectshit; j++) - { - if (hitobjects[j] == ents[i]) - balreadyhit = true; - } - if (balreadyhit) - continue; - - // check so we are sufficiently close - if (distance(ents[i]->pos, player->pos) > (player->phys_size * 2.0f)) - continue; - - // hit a player, give him damage and stuffs... - create_sound(ents[i]->pos, sound_impact); - // set his velocity to fast upward (for now) - hitobjects[numobjectshit++] = ents[i]; - ents[i]->take_damage(vec2(0,10.0f), damage, player->client_id); - } - } - return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY | MODIFIER_RETURNFLAGS_OVERRIDEPOSITION|MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY; - } - - - // move char, and check intersection from us to target - return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON | MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY; - } - - int activateninja(player* player) - { - // ok then, activate ninja - activationdir = player->direction; - currentactivation = activationtime; - currentmovetime = movetime; - currentcooldown = cooldown; - // reset hit objects - numobjectshit = 0; - - create_sound(player->pos, SOUND_FIRE_NINJA); - - return MODIFIER_RETURNFLAGS_OVERRIDEWEAPON; - } - - int activate(player* player) - { - if (flags & MODIFIER_NEEDSACTIVATION) - { - if (!numactivations) - return 0; - numactivations--; - } - - switch (modifiertype) - { - case MODIFIER_TYPE_NINJA: - { - return activateninja(player); - } - /*case MODIFIER_TYPE_TIMEFIELD: - { - updatetimefield(); - break; - }*/ - default: - return 0; - } - } - int update(player* player) - { - switch (modifiertype) - { - case MODIFIER_TYPE_NINJA: - { - return updateninja(player); - } - /*case MODIFIER_TYPE_TIMEFIELD: - { - updatetimefield(); - break; - }*/ - default: - return 0; - } - } - }; - - enum - { - PLAYER_FLAGS_ISRELOADING = 1 << 0, - PLAYER_FLAGS_ISEQUIPPING = 1 << 1, - }; - - weapon lweapons[WEAPON_NUMWEAPONS]; - modifier modifiers[MODIFIER_NUMMODIFIERS]; - int iactiveweapon; - int inextweapon; - int equip_time; - - int client_id; - int flags; - - char name[32]; - player_input previnput; - player_input input; - int tick_count; - int damage_taken_tick; - + bool got; + int ammo; + } weapons[NUM_WEAPONS]; + int active_weapon; + int reload_timer; + int attack_tick; + + // we need a defered position so we can handle the physics correctly + vec2 defered_pos; vec2 vel; vec2 direction; + // + int client_id; + char name[32]; + + // input + player_input previnput; + player_input input; int jumped; - int airjumped; - - //int firing; - int hooking; - - int fire_timeout; - int reload_timeout; + + int damage_taken_tick; int health; int armor; int score; - // sounds - int sound_player_jump; - int sound_player_land; - int sound_player_hurt_short; - int sound_player_hurt_long; - int sound_player_spawn; - int sound_player_chain_loop; - int sound_player_chain_impact; - int sound_player_impact; - int sound_player_impact_ninja; - int sound_player_die; - int sound_player_switchweapon; - - player* phookedplayer; - powerup* phookedpowerup; - int numhooked; + // hooking stuff + enum + { + HOOK_RETRACTED=-1, + HOOK_IDLE=0, + HOOK_FLYING, + HOOK_GRABBED + }; + + int hook_state; + player *hooked_player; vec2 hook_pos; vec2 hook_dir; - player() : - entity(OBJTYPE_PLAYER) + // + player() + : entity(OBJTYPE_PLAYER) { reset(); - - //firing = 0; - // setup weaponflags and stuff - lweapons[WEAPON_TYPE_GUN].setgun(); - lweapons[WEAPON_TYPE_ROCKET].setrocket(); - //lweapons[WEAPON_TYPE_SNIPER].setsniper(); - lweapons[WEAPON_TYPE_SHOTGUN].setshotgun(); - lweapons[WEAPON_TYPE_MELEE].setmelee(); - - modifiers[MODIFIER_TYPE_NINJA].setninja(); - modifiers[MODIFIER_TYPE_TIMEFIELD].settimefield(); - //modifiers[MODIFIER_TYPE_NINJA].flags |= MODIFIER_ISACTIVE; - - sound_player_jump = SOUND_PLAYER_JUMP; - sound_player_hurt_short = SOUND_PLAYER_HURT_SHORT; - sound_player_hurt_long = SOUND_PLAYER_HURT_LONG; - sound_player_spawn = SOUND_PLAYER_SPAWN; - sound_player_chain_loop = SOUND_PLAYER_CHAIN_LOOP; - sound_player_chain_impact = SOUND_PLAYER_CHAIN_IMPACT; - sound_player_impact = SOUND_PLAYER_IMPACT; - sound_player_impact_ninja = SOUND_PLAYER_IMPACT_NINJA; - sound_player_die = SOUND_PLAYER_DIE; - sound_player_switchweapon = SOUND_PLAYER_SWITCHWEAPON; - sound_player_land = SOUND_PLAYER_LAND; } void reset() { - equip_time = 0; - phookedplayer = 0; - numhooked = 0; + //equip_time = 0; + + release_hooked(); + release_hooks(); + proximity_radius = phys_size; name[0] = 'n'; name[1] = 'o'; @@ -1065,32 +495,21 @@ public: vel = vec2(0.0f, 0.0f); direction = vec2(0.0f, 1.0f); client_id = -1; - tick_count = 0; score = 0; - flags = 0; } - virtual void destroy() { flags = 0; } + virtual void destroy() { } void respawn() { health = PLAYER_MAXHEALTH; armor = 0; - - hooking = 0; - phookedplayer = 0; - phookedpowerup = 0; - numhooked = 0; - fire_timeout = 0; - reload_timeout = 0; - iactiveweapon = 0; - inextweapon = -1; - equip_time = 0; jumped = 0; - airjumped = 0; + mem_zero(&input, sizeof(input)); vel = vec2(0.0f, 0.0f); + // get spawn point int start, num; map_get_type(1, &start, &num); @@ -1101,30 +520,17 @@ public: } else pos = vec2(100.0f, -60.0f); - - // reset active flags - for (int i = 0; i < WEAPON_NUMWEAPONS; i++) - { - // reset and remove - lweapons[i].settype(); - lweapons[i].flags &= ~WEAPON_ISACTIVE; - } - - - // TEMP REMOVE + + // init weapons + mem_zero(&weapons, sizeof(weapons)); + weapons[WEAPON_HAMMER].got = true; + weapons[WEAPON_HAMMER].ammo = -1; + weapons[WEAPON_GUN].got = true; + weapons[WEAPON_GUN].ammo = 10; + active_weapon = WEAPON_GUN; + reload_timer = 0; - /*for (int i = 0; i < WEAPON_NUMWEAPONS; i++) - { - lweapons[i].settype(); - lweapons[i].flags |= WEAPON_ISACTIVE; - }*/ - lweapons[WEAPON_TYPE_MELEE].flags |= WEAPON_ISACTIVE; - // Add gun as default weapon - iactiveweapon = WEAPON_TYPE_GUN; - lweapons[WEAPON_TYPE_GUN].numammo = 10; - lweapons[WEAPON_TYPE_GUN].flags |= WEAPON_ISACTIVE; - - create_sound(pos, sound_player_spawn); + create_sound(pos, SOUND_PLAYER_SPAWN); } bool is_grounded() @@ -1136,470 +542,261 @@ public: return false; } - // Disable weapon activation if this returns true - int handlemodifiers() + // releases the hooked player + void release_hooked() { - int returnflags = 0; - for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) - { - if (modifiers[i].flags & MODIFIER_ISACTIVE) - { - modifiers[i].duration--; - modifiers[i].currentcooldown--; - - // Check if it should activate - if (modifiers[i].currentcooldown <= 0 && - (modifiers[i].flags & MODIFIER_NEEDSACTIVATION) && - input.fire && !(previnput.fire)) - { - returnflags |= modifiers[i].activate(this); - } - - returnflags |= modifiers[i].update(this); - - // remove active if timed out - if (modifiers[i].duration <= 0 && modifiers[i].currentactivation <= 0) - modifiers[i].flags &= ~MODIFIER_ISACTIVE; - } - } - return returnflags; + hook_state = HOOK_IDLE; + hooked_player = 0x0; } - void handleweapon() + // release all hooks to this player + void release_hooks() { - // handle weapon - if(input.fire && (!previnput.fire || lweapons[iactiveweapon].flags & WEAPON_AUTOFIRE) && - !(flags & PLAYER_FLAGS_ISEQUIPPING) && !reload_timeout) + // TODO: loop thru players only + for(entity *ent = world.first_entity; ent; ent = ent->next_entity) { - if(fire_timeout == 0) + if(ent && ent->objtype == OBJTYPE_PLAYER) { - if (lweapons[iactiveweapon].numammo || !(lweapons[iactiveweapon].flags & WEAPON_DRAWSAMMO)) - { - // Decrease ammo - fire_timeout = lweapons[iactiveweapon].activate(this); - } - else if ((lweapons[iactiveweapon].flags & WEAPON_NEEDRELOAD) && lweapons[iactiveweapon].nummagazines) - { - // reload - reload_timeout = lweapons[iactiveweapon].reloadtime; - lweapons[iactiveweapon].nummagazines--; - lweapons[iactiveweapon].numammo = lweapons[iactiveweapon].magsize; - } + player *p = (player*)ent; + if(p->hooked_player == this) + p->release_hooked(); } } - - // update active weapon - lweapons[iactiveweapon].update(this, fire_timeout); } - - void handlehook() + + void handle_weapons() { - // handle hook - if(input.hook) + // check reload timer + if(reload_timer) { - if(hooking == 0) - { - hooking = 1; - hook_pos = pos; - hook_dir = direction; - // Sound - create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STARTLOOP); - } - else if(hooking == 1) - { - vec2 new_pos = hook_pos+hook_dir*hook_fire_speed; + reload_timer--; + return; + } - // Check against other players and powerups first - player* targetplayer = 0; - powerup* targetpowerup = 0; + // switch weapon if wanted + if(input.activeweapon >= 0 && input.activeweapon < NUM_WEAPONS && weapons[input.activeweapon].got) + active_weapon = input.activeweapon; + + if(input.fire) + { + if(reload_timer == 0) + { + // fire! + if(weapons[active_weapon].ammo) { - static const int typelist[2] = { OBJTYPE_PLAYER, OBJTYPE_POWERUP}; - entity *ents[64]; - vec2 dir = new_pos - hook_pos; - float radius = length(dir * 0.5f); - vec2 center = hook_pos + dir * 0.5f; - int num = world.find_entities(center, radius, ents, 64,typelist,2); + switch(active_weapon) + { + case WEAPON_HAMMER: + break; + + case WEAPON_GUN: + new projectile(WEAPON_PROJECTILETYPE_GUN, + client_id, + pos+vec2(0,0), + direction*30.0f, + 100, + this, + 1, 0, 0, -1); + break; + case WEAPON_ROCKET: + new projectile(WEAPON_PROJECTILETYPE_ROCKET, + client_id, + pos+vec2(0,0), + direction*15.0f, + 100, + this, + 1, projectile::PROJECTILE_FLAGS_EXPLODE, 0, -1); + break; + case WEAPON_SHOTGUN: + for(int i = 0; i < 3; i++) + { + new projectile(WEAPON_PROJECTILETYPE_SHOTGUN, + client_id, + pos+vec2(0,0), + direction*(20.0f+(i+1)*2.0f), + 100, + this, + 1, 0, 0, -1); + } + break; + } - for (int i = 0; i < num; i++) - { - // Check if entity is a player - if (ents[i] == this || (targetplayer && targetpowerup)) - continue; - - if (!targetplayer && ents[i]->objtype == OBJTYPE_PLAYER) - { - // temp, set hook pos to our position - if (((player*)ents[i])->phookedplayer != this) - targetplayer = (player*)ents[i]; - } - else if (!targetpowerup && ents[i]->objtype == OBJTYPE_POWERUP && - (((powerup*)ents[i])->flags & powerup::POWERUP_FLAG_HOOKABLE)) - { - targetpowerup = (powerup*)ents[i]; - } - } - } - - //player* targetplayer = intersect_player(hook_pos, hook_pos, new_pos, this); - if (targetplayer) - { - // So he can't move "normally" - new_pos = targetplayer->pos; - phookedplayer = targetplayer; - targetplayer->numhooked++; - hooking = 3; - create_sound(pos, sound_player_chain_impact); - - // stop looping chain sound - create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); - } - else if (targetpowerup) - { - new_pos = targetpowerup->pos; - phookedpowerup = targetpowerup; - phookedpowerup->playerhooked = this; - hooking = 4; - create_sound(pos, sound_player_chain_impact); - - // stop looping chain sound - create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); - } - else if(intersect_line(hook_pos, new_pos, &new_pos)) - { - hooking = 2; - create_sound(pos, sound_player_chain_impact); - - // stop looping chain sound - create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); - } - else if(distance(pos, new_pos) > hook_length) - { - hooking = -1; - create_sound(pos, sound_player_chain_loop, SOUND_LOOPFLAG_STOPLOOP); - } - - hook_pos = new_pos; - } - else if(hooking == 2) - { - vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; - // the hook as more power to drag you up then down. - // this makes it easier to get on top of an platform - if(hookvel.y > 0) - hookvel.y *= 0.3f; - - // the hook will boost it's power if the player wants to move - // in that direction. otherwise it will dampen everything abit - if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) - hookvel.x *= 0.95f; - else - hookvel.x *= 0.75f; - vec2 new_vel = vel+hookvel; - - // check if we are under the legal limit for the hook - if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) - vel = new_vel; // no problem. apply - } - else if (hooking == 3) - { - // hmm, force the targetplayer towards us if possible, otherwise us towards them if they are already hooked - if (phookedplayer) - { - if (phookedplayer->hooking > 1) - { - // Drag us towards target player - vec2 hookvel = normalize(hook_pos-pos)*hook_drag_accel; - if((hookvel.x < 0 && input.left) || (hookvel.x > 0 && input.right)) - hookvel.x *= 0.95f; - else - hookvel.x *= 0.75f; - - // Apply the velocity - // the hook will boost it's power if the player wants to move - // in that direction. otherwise it will dampen everything abit - vec2 new_vel = vel+hookvel; - - // check if we are under the legal limit for the hook - if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) - vel = new_vel; // no problem. apply - } - else - { - // Drag targetplayer towards us - vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel; - - // Apply the velocity - // the hook will boost it's power if the player wants to move - // in that direction. otherwise it will dampen everything abit - vec2 new_vel = phookedplayer->vel+hookvel; - - // check if we are under the legal limit for the hook - if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) - phookedplayer->vel = new_vel; // no problem. apply - } - hook_pos = phookedplayer->pos; - // if hooked player dies, release the hook + weapons[active_weapon].ammo--; } else { - hooking = -1; - phookedplayer = 0; + // click!!! click } - } - else if (hooking == 4) - { - // Have a powerup, drag it towards us - vec2 hookvel = normalize(pos-hook_pos)*hook_drag_accel; - - // Apply the velocity - // the hook will boost it's power if the player wants to move - // in that direction. otherwise it will dampen everything abit - vec2 new_vel = phookedpowerup->vel+hookvel; - // check if we are under the legal limit for the hook - if(length(new_vel) < hook_drag_speed || length(new_vel) < length(vel)) - phookedpowerup->vel = new_vel; // no problem. apply - hook_pos = phookedpowerup->pos; + attack_tick = server_tick(); + reload_timer = 10; // make this variable depending on weapon } } - else - { - hooking = 0; - hook_pos = pos; - if (phookedplayer) - { - phookedplayer->numhooked--; - phookedplayer = 0; - } - } - } - - void getattackticks(int& curattack, int& attacklen, int& visualtimeattack) - { - // time left from current attack (if any) - // first check modifiers (ninja...) - for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) - { - if ((modifiers[i].flags & (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION)) == (MODIFIER_ISACTIVE | MODIFIER_NEEDSACTIVATION)) - { - curattack = modifiers[i].currentactivation; - attacklen = modifiers[i].activationtime; - visualtimeattack = modifiers[i].visualtimeattack; - return; - } - } - - // otherwise current fire timeout - curattack = fire_timeout; - attacklen = lweapons[iactiveweapon].firetime; - visualtimeattack = lweapons[iactiveweapon].visualtimeattack; } virtual void tick() { - tick_count++; + // TODO: rework the input to be more robust + // TODO: remove this tick count, it feels weird // fetch some info bool grounded = is_grounded(); direction = get_direction(input.angle); - // decrease reload timer - if(fire_timeout) - fire_timeout--; - if (reload_timeout) - reload_timeout--; - - // Switch weapons - if (flags & PLAYER_FLAGS_ISEQUIPPING) - { - equip_time--; - if (equip_time <= 0) - { - if (inextweapon >= 0) - { - equip_time = SERVER_TICK_SPEED * lweapons[inextweapon].equiptime; - iactiveweapon = inextweapon; - inextweapon = -1; - - // Send switch weapon event to client? - } - else - { - flags &= ~PLAYER_FLAGS_ISEQUIPPING; - } - } - } - else if (input.activeweapon && (unsigned int)iactiveweapon != (input.activeweapon & ~0x80000000)) - { - input.activeweapon &= ~0x80000000; - // check which weapon to activate - if (input.activeweapon >= 0 && input.activeweapon < WEAPON_NUMWEAPONS && - (lweapons[input.activeweapon].flags & WEAPON_ISACTIVE)) - { - inextweapon = input.activeweapon; - equip_time = SERVER_TICK_SPEED * lweapons[iactiveweapon].unequiptime; - // unequip current - flags |= PLAYER_FLAGS_ISEQUIPPING; - - create_sound(pos, sound_player_switchweapon); - } - } - - // don't do any weapon activations if modifier is currently overriding - int modifierflags = handlemodifiers(); - if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEWEAPON)) - handleweapon(); - - handlehook(); + float max_speed = grounded ? ground_control_speed : air_control_speed; + float accel = grounded ? ground_control_accel : air_control_accel; + float friction = grounded ? ground_friction : air_friction; // handle movement - if(grounded) - { - if (airjumped) - create_sound(pos, SOUND_PLAYER_LAND); - airjumped = 0; - } - - float elast = 0.0f; - - if (!numhooked) - { - // I'm hooked by someone, so don't do any movement plz (temp) - if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEVELOCITY)) - { - if(grounded) - { - // ground movement - if(input.left) - { - if(vel.x > -ground_control_speed) - { - vel.x -= ground_control_accel; - if(vel.x < -ground_control_speed) - vel.x = -ground_control_speed; - } - } - else if(input.right) - { - if(vel.x < ground_control_speed) - { - vel.x += ground_control_accel; - if(vel.x > ground_control_speed) - vel.x = ground_control_speed; - } - } - else - vel.x *= ground_friction; // ground fiction - } - else - { - // air movement - if(input.left) - { - if(vel.x > -air_control_speed) - vel.x -= air_control_accel; - } - else if(input.right) - { - if(vel.x < air_control_speed) - vel.x += air_control_accel; - } - else - vel.x *= air_friction; // air fiction - } - - if(input.jump) - { - if(jumped == 0) - { - if(grounded) - { - create_sound(pos, sound_player_jump); - vel.y = -ground_jump_speed; - jumped++; - } - /* - else if(airjumped == 0) - { - vel.y = -12; - airjumped++; - jumped++; - }*/ - } - else if (!grounded) - { - airjumped++; - } - } - else - jumped = 0; - } - - // meh, go through all players and stop their hook on me - /* - for(entity *ent = world.first_entity; ent; ent = ent->next_entity) - { - if (ent && ent->objtype == OBJTYPE_PLAYER) - { - player *p = (player*)ent; - if(p != this) - { - float d = distance(pos, p->pos); - vec2 dir = normalize(pos - p->pos); - if(d < phys_size*1.5f) - { - float a = phys_size*1.5f - d; - vel = vel + dir*a; - } - - if(p->phookedplayer == this) - { - if(d > phys_size*2.5f) - { - elast = 0.0f; - vel = vel - dir*2.5f; - } - } - } - } - }*/ + if(input.left) + vel.x = saturated_add(-max_speed, max_speed, vel.x, -accel); + if(input.right) + vel.x = saturated_add(-max_speed, max_speed, vel.x, accel); - // gravity - if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEGRAVITY)) - vel.y += gravity; + if(!input.left && !input.right) + vel.x *= friction; + + // handle jumping + if(input.jump) + { + if(!jumped && grounded) + { + create_sound(pos, SOUND_PLAYER_JUMP); + vel.y = -ground_jump_speed; + jumped++; + } + } + else + jumped = 0; + + // do hook + if(input.hook) + { + if(hook_state == HOOK_IDLE) + { + hook_state = HOOK_FLYING; + hook_pos = pos; + hook_dir = direction; + } + else if(hook_state == HOOK_FLYING) + { + vec2 new_pos = hook_pos+hook_dir*hook_fire_speed; + + // Check against other players first + for(entity *ent = world.first_entity; ent; ent = ent->next_entity) + { + if(ent && ent->objtype == OBJTYPE_PLAYER) + { + player *p = (player*)ent; + if(p != this && distance(p->pos, new_pos) < p->phys_size) + { + hook_state = HOOK_GRABBED; + hooked_player = p; + break; + } + } + } + + if(hook_state == HOOK_FLYING) + { + // check against ground + if(col_intersect_line(hook_pos, new_pos, &new_pos)) + { + hook_state = HOOK_GRABBED; + hook_pos = new_pos; + } + else if(distance(pos, new_pos) > hook_length) + { + hook_state = HOOK_RETRACTED; + } + else + hook_pos = new_pos; + } + + if(hook_state == HOOK_GRABBED) + create_sound(pos, SOUND_HOOK_ATTACH); + } + } + else + { + release_hooked(); + hook_pos = pos; + } + + if(hook_state == HOOK_GRABBED) + { + if(hooked_player) + hook_pos = hooked_player->pos; + + float d = distance(pos, hook_pos); + vec2 dir = normalize(pos - hook_pos); + if(d > 10.0f) // TODO: fix tweakable variable + { + float accel = hook_drag_accel * (d/hook_length); + vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x*0.75f); + vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y); + } + } + + // fix influence of other players, collision + hook + // TODO: loop thru players only + for(entity *ent = world.first_entity; ent; ent = ent->next_entity) + { + if(ent && ent->objtype == OBJTYPE_PLAYER) + { + player *p = (player*)ent; + if(p == this) + continue; // make sure that we don't nudge our self + + // handle player <-> player collision + float d = distance(pos, p->pos); + vec2 dir = normalize(pos - p->pos); + if(d < phys_size*1.25f) + { + float a = phys_size*1.25f - d; + vel = vel + dir*a; + } + + // handle hook influence + if(p->hooked_player == this) + { + if(d > phys_size*1.50f) // TODO: fix tweakable variable + { + float accel = hook_drag_accel * (d/hook_length); + vel.x = saturated_add(-hook_drag_speed, hook_drag_speed, vel.x, -accel*dir.x); + vel.y = saturated_add(-hook_drag_speed, hook_drag_speed, vel.y, -accel*dir.y); + } + } + } } - if (!(modifierflags & MODIFIER_RETURNFLAGS_OVERRIDEPOSITION)) - move_box(&pos, &vel, vec2(phys_size, phys_size), elast); + // handle weapons + handle_weapons(); + + // add gravity + vel.y += gravity; + + // do the move + defered_pos = pos; + move_box(&defered_pos, &vel, vec2(phys_size, phys_size), 0); + return; + } + + virtual void tick_defered() + { + // apply the new position + pos = defered_pos; } void die() { - create_sound(pos, sound_player_die); - // release our hooked player - if (phookedplayer) - { - phookedplayer->numhooked--; - phookedplayer = 0; - hooking = -1; - numhooked = 0; - } + create_sound(pos, SOUND_PLAYER_DIE); + + release_hooked(); + release_hooks(); + + // TODO: insert timer here respawn(); - - // meh, go through all players and stop their hook on me - for(entity *ent = world.first_entity; ent; ent = ent->next_entity) - { - if (ent && ent->objtype == OBJTYPE_PLAYER) - { - player* p = (player*)ent; - if (p->phookedplayer == this) - { - p->phookedplayer = 0; - p->hooking = -1; - //p->numhooked--; - } - } - } } virtual bool take_damage(vec2 force, int dmg, int from) @@ -1620,24 +817,11 @@ public: } else armor -= dmg; - /* - int armordmg = (dmg+1)/2; - int healthdmg = dmg-armordmg; - if(armor < armordmg) - { - healthdmg += armordmg - armor; - armor = 0; - } - else - armor -= armordmg; - - health -= healthdmg; - */ // create healthmod indicator create_healthmod(pos, dmg); - damage_taken_tick = tick_count+50; + damage_taken_tick = server_tick()+50; // check for death if(health <= 0) @@ -1659,12 +843,11 @@ public: } if (dmg > 2) - create_sound(pos, sound_player_hurt_long); + create_sound(pos, SOUND_PLAYER_PAIN_LONG); else - create_sound(pos, sound_player_hurt_short); + create_sound(pos, SOUND_PLAYER_PAIN_SHORT); // spawn blood? - return true; } @@ -1672,33 +855,20 @@ public: { obj_player *player = (obj_player *)snap_new_item(OBJTYPE_PLAYER, client_id, sizeof(obj_player)); - client_info info; - if(server_getclientinfo(client_id, &info)) - snap_encode_string(info.name, player->name, strlen(info.name), 32); - player->x = (int)pos.x; player->y = (int)pos.y; player->vx = (int)vel.x; player->vy = (int)vel.y; player->emote = EMOTE_NORMAL; - player->ammocount = lweapons[iactiveweapon].numammo; + player->ammocount = weapons[active_weapon].ammo; player->health = 0; player->armor = 0; player->local = 0; player->clientid = client_id; - player->weapon = iactiveweapon; - player->modifier = 0; - for (int i = 0; i < MODIFIER_NUMMODIFIERS; i++) - { - // add active modifiers - if (modifiers[i].flags & MODIFIER_ISACTIVE) - player->modifier |= 1 << i; - } - // get current attack ticks - getattackticks(player->attackticks, player->attacklen, player->visualtimeattack); + player->weapon = active_weapon; + player->attacktick = attack_tick; - if(client_id == snaping_client) { player->local = 1; @@ -1709,16 +879,16 @@ public: if(length(vel) > 15.0f) player->emote = EMOTE_HAPPY; - if(damage_taken_tick > tick_count) + if(damage_taken_tick > server_tick()) player->emote = EMOTE_PAIN; if(player->emote == EMOTE_NORMAL) { - if((tick_count%(50*5)) < 10) + if((server_tick()%(50*5)) < 10) player->emote = EMOTE_BLINK; } - player->hook_active = hooking>0?1:0; + player->hook_active = hook_state>0?1:0; player->hook_x = (int)hook_pos.x; player->hook_y = (int)hook_pos.y; @@ -1729,66 +899,20 @@ public: // POWERUP /////////////////////// -powerup::powerup(int _type, int _subtype, int _numitems, int _flags) : - entity(OBJTYPE_POWERUP) +powerup::powerup(int _type, int _subtype) +: entity(OBJTYPE_POWERUP) { - static int current_id = 0; - playerhooked = 0; - id = current_id++; - vel = vec2(0.0f,0.0f); + //static int current_id = 0; type = _type; subtype = _subtype; - numitems = _numitems; - flags = _flags; // set radius (so it can collide and be hooked and stuff) proximity_radius = phys_size; spawntick = -1; + + // TODO: should this be done here? world.insert_entity(this); } -void powerup::spawnrandom(int _lifespan) -{ - return; - /* - vec2 pos; - int start, num; - map_get_type(1, &start, &num); - - if(!num) - return; - - mapres_spawnpoint *sp = (mapres_spawnpoint*)map_get_item(start + (rand()%num), NULL, NULL); - pos = vec2(sp->x, sp->y); - - // Check if there already is a powerup at that location - { - int type = OBJTYPE_POWERUP; - entity *ents[64]; - int num = world.find_entities(pos, 5.0f, ents, 64,&type,1); - for (int i = 0; i < num; i++) - { - if (ents[i]->objtype == OBJTYPE_POWERUP) - { - // location busy - return; - } - } - } - - powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan); - ppower->pos = pos; - if (ppower->type == POWERUP_TYPE_WEAPON) - { - ppower->subtype = rand() % WEAPON_NUMWEAPONS; - ppower->numitems = 10; - } - else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR) - { - ppower->numitems = rand() % 5; - } - ppower->flags |= POWERUP_FLAG_HOOKABLE;*/ -} - void powerup::tick() { // wait for respawn @@ -1799,84 +923,47 @@ void powerup::tick() else return; } - - vec2 oldpos = pos; - //vel.y += 0.25f; - pos += vel; - move_box(&pos, &vel, vec2(phys_size, phys_size), 0.0f); - // Check if a player intersected us vec2 meh; player* pplayer = intersect_player(pos, pos + vec2(0,16), meh, 0); if (pplayer) { // player picked us up, is someone was hooking us, let them go - if (playerhooked) - playerhooked->hooking = -1; int respawntime = -1; switch (type) { case POWERUP_TYPE_HEALTH: + if(pplayer->health < PLAYER_MAXHEALTH) { - if(pplayer->health < PLAYER_MAXHEALTH) - { - pplayer->health = min(PLAYER_MAXHEALTH, pplayer->health + numitems); - respawntime = 20; - } - break; + pplayer->health = min((int)PLAYER_MAXHEALTH, pplayer->health + 1); + respawntime = 20; } + break; case POWERUP_TYPE_ARMOR: + if(pplayer->armor < PLAYER_MAXARMOR) { - if(pplayer->armor < PLAYER_MAXARMOR) - { - pplayer->armor = min(PLAYER_MAXARMOR, pplayer->armor + numitems); - respawntime = 20; - } - break; + pplayer->armor = min((int)PLAYER_MAXARMOR, pplayer->armor + 1); + respawntime = 20; } + break; + case POWERUP_TYPE_WEAPON: + if(subtype >= 0 && subtype < NUM_WEAPONS) { - if (pplayer->lweapons[subtype].flags & player::WEAPON_ISACTIVE) + if(pplayer->weapons[subtype].ammo < 10 || !pplayer->weapons[subtype].got) { - // add ammo - /* - if (pplayer->lweapons[subtype].flags & player::WEAPON_NEEDRELOAD) - { - int numtoadd = min(numitems, pplayer->lweapons[subtype].magsize); - pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numtoadd); - pplayer->lweapons[subtype].nummagazines += (numitems - numtoadd) % pplayer->lweapons[subtype].magsize; - } - else*/ - if(pplayer->lweapons[subtype].numammo < 10) - { - respawntime = 20; - pplayer->lweapons[subtype].numammo = min(10, pplayer->lweapons[subtype].numammo + numitems); - } - } - else - { - pplayer->lweapons[subtype].settype(); - pplayer->lweapons[subtype].flags |= player::WEAPON_ISACTIVE; + pplayer->weapons[subtype].got = true; + pplayer->weapons[subtype].ammo = min(10, pplayer->weapons[subtype].ammo + 5); respawntime = 20; } - break; } - case POWERUP_TYPE_NINJA: - { - respawntime = 60; - // reset and activate - pplayer->modifiers[MODIFIER_TYPE_NINJA].settype(); - pplayer->modifiers[MODIFIER_TYPE_NINJA].flags |= player::MODIFIER_ISACTIVE; - break; - } - //POWERUP_TYPE_TIMEFIELD = 4, + break; default: break; }; if(respawntime >= 0) spawntick = server_tick() + server_tickspeed() * respawntime; - //world.destroy_entity(this); } } @@ -1885,18 +972,17 @@ void powerup::snap(int snapping_client) if(spawntick != -1) return; - obj_powerup *powerup = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup)); - powerup->x = (int)pos.x; - powerup->y = (int)pos.y; - powerup->vx = (int)vel.x; - powerup->vy = (int)vel.y; - powerup->type = type; - powerup->subtype = subtype; + obj_powerup *up = (obj_powerup *)snap_new_item(OBJTYPE_POWERUP, id, sizeof(obj_powerup)); + up->x = (int)pos.x; + up->y = (int)pos.y; + up->type = type; // TODO: two diffrent types? what gives? + up->subtype = subtype; } // POWERUP END /////////////////////// -static player players[MAX_CLIENTS]; +static const int NUM_BOTS = 1; +static player players[MAX_CLIENTS+NUM_BOTS]; player *get_player(int index) { @@ -1933,19 +1019,7 @@ void create_explosion(vec2 p, int owner, bool bnodamage) float l = length(diff); float dmg = 5 * (1 - (l/radius)); if((int)dmg) - { - ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner);/* && - ents[i]->objtype == OBJTYPE_PLAYER && - owner >= 0) - { - player *p = (player*)ents[i]; - if(p->client_id == owner) - p->score--; - else - ((player*)ents[owner])->score++; - - }*/ - } + ents[i]->take_damage(forcedir*dmg*2, (int)dmg, owner); } } } @@ -1970,6 +1044,7 @@ void create_sound(vec2 pos, int sound, int loopingflags) ev->sound = sound | loopingflags; } +// TODO: should be more general player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis) { // Find other players @@ -1993,19 +1068,26 @@ player* intersect_player(vec2 pos0, vec2 pos1, vec2& new_pos, entity* notthis) } // Server hooks -static int addtick = SERVER_TICK_SPEED * 5; void mods_tick() { // clear all events events.clear(); world.tick(); - if (addtick <= 0) + if(debug_bots) { - powerup::spawnrandom(SERVER_TICK_SPEED * 5); - addtick = SERVER_TICK_SPEED * 5; + static int count = 0; + if(count >= 0) + { + count++; + if(count == 10) + { + for(int i = 0; i < NUM_BOTS; i++) + mods_client_enter(MAX_CLIENTS+i); + count = -1; + } + } } - addtick--; } void mods_snap(int client_id) @@ -2026,7 +1108,6 @@ void mods_client_enter(int client_id) players[client_id].client_id = client_id; players[client_id].respawn(); world.insert_entity(&players[client_id]); - } void mods_client_drop(int client_id) @@ -2042,13 +1123,13 @@ void mods_init() int start, num; map_get_type(MAPRES_ITEM, &start, &num); + // TODO: this is way more complicated then it should be for(int i = 0; i < num; i++) { mapres_item *it = (mapres_item *)map_get_item(start+i, 0, 0); int type = -1; int subtype = -1; - int numitems = 1; switch(it->type) { @@ -2059,17 +1140,11 @@ void mods_init() case ITEM_WEAPON_SHOTGUN: type = POWERUP_TYPE_WEAPON; subtype = WEAPON_TYPE_SHOTGUN; - numitems = 5; break; case ITEM_WEAPON_ROCKET: type = POWERUP_TYPE_WEAPON; subtype = WEAPON_TYPE_ROCKET; - numitems = 5; break; - /*case ITEM_WEAPON_SNIPER: - type = POWERUP_TYPE_WEAPON; - subtype = WEAPON_TYPE_ROCKET; - break;*/ case ITEM_WEAPON_HAMMER: type = POWERUP_TYPE_WEAPON; subtype = WEAPON_TYPE_MELEE; @@ -2077,53 +1152,16 @@ void mods_init() case ITEM_HEALTH_1: type = POWERUP_TYPE_HEALTH; - numitems = 1; - break; - case ITEM_HEALTH_5: - type = POWERUP_TYPE_HEALTH; - numitems = 5; - break; - case ITEM_HEALTH_10: - type = POWERUP_TYPE_HEALTH; - numitems = 10; break; case ITEM_ARMOR_1: type = POWERUP_TYPE_ARMOR; - numitems = 1; - break; - case ITEM_ARMOR_5: - type = POWERUP_TYPE_ARMOR; - numitems = 5; - break; - case ITEM_ARMOR_10: - type = POWERUP_TYPE_ARMOR; - numitems = 10; break; }; - powerup* ppower = new powerup(type, subtype, numitems); - ppower->pos.x = it->x; - ppower->pos.y = it->y; + powerup *ppower = new powerup(type, subtype); + ppower->pos = vec2(it->x, it->y); } - - - /* - powerup* ppower = new powerup(rand() % POWERUP_TYPE_NUMPOWERUPS,_lifespan); - ppower->pos = pos; - if (ppower->type == POWERUP_TYPE_WEAPON) - { - ppower->subtype = rand() % WEAPON_NUMWEAPONS; - ppower->numitems = 10; - } - else if (ppower->type == POWERUP_TYPE_HEALTH || ppower->type == POWERUP_TYPE_ARMOR) - { - ppower->numitems = rand() % 5; - } - ppower->flags |= POWERUP_FLAG_HOOKABLE; - */ - - //powerup::spawnrandom(SERVER_TICK_SPEED * 5); } void mods_shutdown() {}