ecs = require("ecs") shim = require("shader_shim") win = require("window") sprites = require("world.sprites") world = require("world") color = require("color") bobber = require("bobber") settings = require("settings") aspect = win.width / win.height aspect_inv = win.height / win.width player_tiny = 6 class Player extends ecs.Entity new: (router, controller, alias) => @router = router @controller = controller if @router.state == "elected" -- We're the host player @alias = alias else error("Implement other players") class PlayerLineGraphicComponent extends world.GraphicsComponent @line_width = 1 @static = false @loctbl = { {-1,-1} {-1,1} {1,1} {1,1} {1,-1} {-1,-1} } buf_size: () => 6 populate_buf: (geom_view, normal_view, offset) => @buf = geom_view h = 0.5 --@lamp_offset = vec4(2.5,1.4,0,0) aspect = win.width / win.height --@lamp = world.level.add_lamp(vec4(0,0,0,0.1 * aspect) + @lamp_offset) uv = sprites.player_normal utbl = { [-1]: {uv.s1, uv.t1} [1]: {uv.s2, uv.t2} } for i = 1,6 loctbl = @@loctbl[i] geom_view[i] = vec3(@@loctbl[i][1] * h, @@loctbl[i][2] * h, -1.5) normal_view[i] = vec2(utbl[@@loctbl[i][1]][1], utbl[@@loctbl[i][2]][2]) join: (entity) => super(entity) @net = entity\get("net") assert(@net, "Failed to find network component on " .. tostring(entity)) @pred = entity\get("pred") @gc = entity\get("graphic") [[ @gc.node\append(@node) ]] s = @ width = 0.01 @node\action(() => --to rotate correcly, we need to start at 00, and set the length -- then rotate the whole thing ce = s.pred.properties.cast_end offset = s.net.properties.cast - s.pred.properties.pos direction = math.atan(offset.x / offset.y) + math.pi if offset.y > 0 direction += math.pi rot = math.rotate4(direction) move2 = vec3(s.pred.properties.pos, 0) scale = s.pred.properties.cast_end z = -0.12 local1 = vec4(-width, 0,z,1) * rot local2 = vec4(-width, scale,z,1) * rot local3 = vec4(width, scale,z,1) * rot local4 = vec4(width, scale,z,1) * rot local5 = vec4(width, 0,z,1) * rot local6 = vec4(-width,0,z,1) * rot s.buf[1] = local1.xyz + move2 s.buf[2] = local2.xyz + move2 s.buf[3] = local3.xyz + move2 s.buf[4] = local4.xyz + move2 s.buf[5] = local5.xyz + move2 s.buf[6] = local6.xyz + move2 if s.net.properties.casted and world.sync_time! - 1 > s.net.properties.cast_time and entity.bobber == nil entity.bobber = bobber.Bobber(nil, s.net.properties.cast) if world.level.on_land(s.net.properties.cast) @action("bobbersfx", am.play(19726308, false, 1, settings.volume)) else @action("bobbersfx", am.play(82979201, false, 1, settings.volume)) print("Should create bobber") elseif not s.net.properties.casted and entity.bobber ~= nil entity.bobber\destroy! @action("bobberretract", am.play(57368801, false, 1, settings.volume)) entity.bobber = nil ) forward: () => --print("Forward called", @properties) super! @gc\move(@properties.pos.x, @properties.pos.y) face: (direction) => @.node("bind").rot = (direction ) % (math.pi * 2) class PlayerGraphicComponent extends world.GraphicsComponent @player_size = 0.25 @static = false buf_size: () => 6 populate_buf: (geom_view, normal_view, offset) => @playerbuf = geom_view h = @@player_size / 2 @lamp_offset = vec4(2.5,1.4,0,0) @lamp = world.level.add_lamp(vec4(0,0,0,1 * aspect) + @lamp_offset) geom_view[1] = vec3(-h,-h,1) geom_view[2] = vec3(-h,h,1) geom_view[3] = vec3(-h,-h,1) geom_view[4] = vec3(h,h,1) geom_view[5] = vec3(h,-h,1) geom_view[6] = vec3(-h,-h,1) uv = sprites.player_normal normal_view[1] = vec2(uv.s1,uv.t1) normal_view[2] = vec2(uv.s1,uv.t2) normal_view[3] = vec2(uv.s2,uv.t2) normal_view[4] = vec2(uv.s2,uv.t2) normal_view[5] = vec2(uv.s2,uv.t1) normal_view[6] = vec2(uv.s1,uv.t1) join: (entity) => super(entity) aspect = win.width / win.height -- inject nodes into the scene graph program = @.node("use_program") pred_component = entity\get("pred") graphic = entity\get("graphic") @node\remove(program) @node\append(am.blend("alpha")\append(program)) @node\action(() => pred_loc = pred_component.properties.pos graphic\move(pred_loc.x, pred_loc.y) ) --@.node("bind").highlight = color.am_color.black move: (x,y) => assert(x, "x required") assert(y, "y required") world.level.move_lamp(@lamp, x + @lamp_offset.x, y + @lamp_offset.y) h = @@player_size / 2 z = -1.5 @playerbuf[1] = vec3(x-h,y-h,z) @playerbuf[2] = vec3(x-h,y+h,z) @playerbuf[3] = vec3(x+h,y+h,z) @playerbuf[4] = vec3(x+h,y+h,z) @playerbuf[5] = vec3(x+h,y-h,z) @playerbuf[6] = vec3(x-h,y-h,z) --print("Move called", @playerbuf[1]) face: (direction) => --print("direction",direction) @.node("bind").rot = (direction ) % (math.pi * 2) -- In a normal simulation, velocity adds acceleration * delta time every tick, minus some friction coefficient * current velocity -- i.e. velocity = (acceleration * delta) - (friction * velocity) -- every tick -- velocity[tick] = (acceleration * delta[tick]) - (friction * velocity[tick - 1]) -- velocity[4] = (acceleration * delta[4]) - (friction * velocity[3]) -- = (acceleration * delta[4]) - (friction * ((acceleration * delta[3]) - (friction * velocity[2]))) -- = (acceleration * delta[4]) - (friction * ((acceleration * delta[3]) - (friction * ((acceleration * delta[2]) - (friction * velocity[inital]))))) -- = (acceleration * delta[4]) - (friction * ((acceleration * delta[3]) - ((friction * acceleration * delta[2]) - (friction * friction * velocity[inital])))) -- = (acceleration * delta[4]) - (friciton * ((acceleration * delta[3]) - (friction * acceleration * delta[2]) + (friction^2 * velocity[inital]))) -- = (acceleration * delta[4]) - ((friction * acceleration * delta[3]) - (friction * friction * acceleration * delta[2]) + (friction^3 * velocity[inital])) -- = (acceleration * delta[4]) - (friction * acceleration * delta[3]) + (friction^2 * acceleration * delta[2]) - (friction^3 * velocity[inital]) -- as delta approaches 0 (high fidelity simulation), the middle components become e^(-friction * delta), and acceleration needs to be divided by friction -- Position is a second layer on top -- position[tick] = position[tick-1] + velocity[tick] -- position[2] = position[inital] + velocity[2] -- = position[inital] + (acceleration * delta[2]) - (friction * velocity[inital]) -- position[delta] = (delta * (acceleration / friction) ) - ((1 / friction) * (velocity[inital] - (acceleratin / friction)) * e^(-friction * delta) + position[inital] -- velocity = (acceleration * delta) - ( -- we want to find the location based on inital velocity and position, constant acceleration, and delta time friction = 0.1 cast_delay = 1 class PlayerPredictedComponent extends ecs.PredictedComponent new: (name) => super(name, {vel: vec2(0,0), pos:vec2(0,0), accel: vec2(0,0), cast_end: 0, can_reel:false}, "net", { accel:() => vec2(@net.properties.accel) * (world.level.on_land(@properties.pos) and 1 or 0.5) vel: () => --print("Net is ", @net.properties) delta = world.sync_time! - @net.properties.last_update (@properties.accel / friction) + ((@net.properties.vel - (@properties.accel / friction)) * math.exp(-friction * delta)) pos: () => delta = world.sync_time! - @net.properties.last_update friction_loss = @properties.accel / friction -- when delta = 0 (up to date) -- pos = (1/friction) * (velocity - friction_loss) * 1 + position -- = 2 * (2 - 2) * 1 + position -- = position (friction_loss * delta) - ((1/friction) * (@properties.vel - friction_loss) * (math.exp(-friction * delta))) + @properties.pos cast_end: () => if not @net.properties.casted return 0 since_cast = world.sync_time! - @net.properties.cast_time dist = math.distance(@properties.pos, @net.properties.cast) len = math.mix(0, dist, since_cast / cast_delay) math.clamp(len,0,dist) can_reel: () => @ent.bobber and @ent.bobber\check! }) print("Right after creation, properties is",@properties) @node = am.group! join: (entity) => @ent = entity @gc = entity\get("graphic") @net = entity\get("net") @gc.node\append(@node) s = @ @node\action(() => s\forward! ) @last_step = am.current_time! forward: () => step_time = 0.4 -- Step sound effect land = world.level.on_land(@properties.pos) if am.current_time! - @last_step > (step_time * (land and 1 or 0.75)) and math.length(@properties.vel) > 0 print("Step playing") if land @node\action("stepsfx", am.play(60861008 + math.random(10) * 100, false, 1, settings.volume)) else @node\action("stepsfx", am.play(78618302 + math.random(8) * 10, false, 1, settings.volume)) @last_step = am.current_time! if @properties.cast_end > 0 and @properties.cast_end < 0.1 @node\action("castsfx", am.play(41228309, false, 1, settings.volume)) --print("Forward called", @properties) super! @gc\move(@properties.pos.x, @properties.pos.y) class ProtoPlayer extends ecs.Entity new: () => @controller = require("controllers.mouse_keyboard") cc = require("controllers.mouse_keyboard").Controller() gc = PlayerGraphicComponent("graphic") pc = PlayerPredictedComponent("pred") nc = ecs.NetworkedComponent("net",{ accel: vec2(0,0), vel: vec2(0,0), pos: vec2(0,0), name: "test", last_update: 0, dir: 0 cast: vec2(0,0) casted: false cast_time: 0 reeling: 0 fish_caught: 0 }) lc = PlayerLineGraphicComponent("line") print("Protoplayer created") super("test",{graphic:gc,net:nc,controller:cc,pred:pc,line:lc}) {:Player, :ProtoPlayer}