aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <git@federicoigne.com>2023-03-05 21:30:20 +0000
committerFederico Igne <git@federicoigne.com>2023-03-05 21:34:30 +0000
commit90d98efdaf7147bf7c704af722c96b95d101b7e8 (patch)
treec144fb8031d35f28356ddc741bdbfa323cd0b55e
parent89f5f6a1c5652205d97c3a47c0aa75f1b67114fb (diff)
downloadraccoon-90d98efdaf7147bf7c704af722c96b95d101b7e8.tar.gz
raccoon-90d98efdaf7147bf7c704af722c96b95d101b7e8.zip
feat: enemies chase entities, get stunned and fly off the screen
This commit introduces the following features: - enemies have a simple target system, chasing a `Pos`. This can be a moving target (e.g., giving them the player's `Pos` reference) or still. - there is a simple status system: entities hit may be "stunned" and, when dead, are "phantom" (i.e., they do not collide with other entities). - when killed, entities fly off the screen.
-rw-r--r--raccoon.lua298
1 files changed, 203 insertions, 95 deletions
diff --git a/raccoon.lua b/raccoon.lua
index df57514..18939a1 100644
--- a/raccoon.lua
+++ b/raccoon.lua
@@ -242,16 +242,18 @@ function Pos:init(x,y)
242 self.y = y or 0 242 self.y = y or 0
243 return self 243 return self
244end 244end
245function Pos:on_ground() 245function Pos:on_ground(phantom)
246 -- Check center tile 246 if not phantom then
247 local center = mget((self.x + 8/2)//8, (self.y + 8)//8) 247 -- Check center tile
248 if fget(center, 0) then return center end 248 local center = mget((self.x + 8/2)//8, (self.y + 8)//8)
249 -- Check left tile 249 if fget(center, 0) then return center end
250 local left = mget((self.x + 1)//8, (self.y + 8)//8) 250 -- Check left tile
251 if fget(left, 0) then return left end 251 local left = mget((self.x + 1)//8, (self.y + 8)//8)
252 -- Check right tile 252 if fget(left, 0) then return left end
253 local right = mget((self.x + 8 - 1)//8, (self.y + 8)//8) 253 -- Check right tile
254 if fget(right, 0) then return right end 254 local right = mget((self.x + 8 - 1)//8, (self.y + 8)//8)
255 if fget(right, 0) then return right end
256 end
255 -- In mid air 257 -- In mid air
256 return nil 258 return nil
257end 259end
@@ -259,6 +261,17 @@ function Pos:to_screen(lpos)
259 return self.x - lpos.x, self.y - lpos.y 261 return self.x - lpos.x, self.y - lpos.y
260end 262end
261 263
264Health = Component("Health")
265function Health:init(max)
266 self.cur = max
267 self.max = max
268 return self
269end
270function Health:damage(d)
271 self.cur = math.max(0,self.cur-d)
272 return self.cur
273end
274
262Movement = Component("Movement") 275Movement = Component("Movement")
263function Movement:init(accel, max) 276function Movement:init(accel, max)
264 -- direction facing 277 -- direction facing
@@ -274,6 +287,12 @@ function Movement:is_moving()
274 return self.cur.x ~= 0 or self.cur.y ~= 0 287 return self.cur.x ~= 0 or self.cur.y ~= 0
275end 288end
276 289
290MovementAI = Component("MovementAI")
291function MovementAI:init()
292 -- TODO
293 return self
294end
295
277Pixel = Component("Pixel") 296Pixel = Component("Pixel")
278function Pixel:init(c) 297function Pixel:init(c)
279 self.color = c 298 self.color = c
@@ -317,12 +336,26 @@ function Entities:init(list)
317 return self 336 return self
318end 337end
319 338
320local Action = Component() 339Status = Component("Status")
321function Action:init(a,b) 340Status.keys = { "phantom", "stunned" }
322 self.a = a 341function Status:init()
323 self.b = b 342 for _,s in ipairs(Status.keys) do self[s] = nil end
324 return self 343 return self
325end 344end
345function Status:tick()
346 for _,s in ipairs(Status.keys) do
347 if self[s] then
348 self[s] = self[s] - 1
349 if self[s] < 0 then
350 self[s] = nil
351 end
352 end
353 end
354end
355for _,s in ipairs(Status.keys) do
356 Status["set_"..s] = function(self,d) self[s] = 60 * (d or 1); return self end
357 Status["is_"..s] = function(self) return self[s] end
358end
326 359
327CircleEffect = Component("CircleEffect") 360CircleEffect = Component("CircleEffect")
328function CircleEffect:init(r2, r1, time, color) 361function CircleEffect:init(r2, r1, time, color)
@@ -362,13 +395,17 @@ function Particles:spawn(pos, entities)
362 end 395 end
363end 396end
364 397
365function ForceField:init(force,range,instant)
366ForceField = Component("ForceField") 398ForceField = Component("ForceField")
399function ForceField:init(force,range,is_target,instant)
367 self.force = force or 1 400 self.force = force or 1
368 self.range = range or 1 401 self.range = range or 1
402 self.is_target = is_target or function(_) return true end
369 self.instant = instant 403 self.instant = instant
370 return self 404 return self
371end 405end
406function ForceField.by_id(id)
407 return function(e) return e.id ~= id end
408end
372 409
373Metadata = Component("Metadata") 410Metadata = Component("Metadata")
374function Metadata:init(name) 411function Metadata:init(name)
@@ -376,6 +413,30 @@ function Metadata:init(name)
376 return self 413 return self
377end 414end
378 415
416Action = Component("Action")
417function Action:init(a,b)
418 self.a = a
419 self.b = b
420 return self
421end
422function Action.forcefield(entity,game)
423 local pos = entity:get_pos()
424 table.insert(
425 game.entities,
426 Entity():with({
427 Pos(pos.x,pos.y),
428 ForceField(2,10,ForceField.by_id(entity.id),true),
429 CircleEffect(20)}))
430end
431
432Target = Component("Target")
433function Target:init(pos, prox, sticky)
434 self.pos = pos
435 self.prox = prox or 5
436 self.sticky = sticky
437 return self
438end
439
379local ForceFieldSystem = System(Pos,ForceField) 440local ForceFieldSystem = System(Pos,ForceField)
380function ForceFieldSystem:exec(entity,enemies) 441function ForceFieldSystem:exec(entity,enemies)
381 local pos = entity:get_pos() 442 local pos = entity:get_pos()
@@ -385,14 +446,31 @@ function ForceFieldSystem:exec(entity,enemies)
385 local pos1 = e:get_pos() 446 local pos1 = e:get_pos()
386 local dist,vec = util.distance(pos,pos1) 447 local dist,vec = util.distance(pos,pos1)
387 if dist < ff.range then 448 if dist < ff.range then
388 if e.has_component[Metadata] then 449 local death_blow = 1
389 local meta = e.get_component[Metadata] 450 if e:has_status() then
390 trace("Hit " .. meta.name .. " at position " .. pos1.x .. " " .. pos1.y) 451 e:get_status():set_stunned(1)
391 else 452 end
392 trace("Hit entity at position " .. pos1.x .. " " .. pos1.y) 453 if e:has_health() then
454 local h = e:get_health():damage(2)
455 if h == 0 then
456 if e:has_status() then
457 e:get_status():set_stunned(3)
458 e:get_status():set_phantom(3)
459 end
460 death_blow = 2 + math.random(2)
461 end
462 end
463 if e:has_movement() then
464 local mov = e:get_movement()
465 mov.cur.x = mov.cur.x + util.signum(vec[1]) * death_blow * ff.force
466 mov.cur.y = mov.cur.y - death_blow * ff.force
393 end 467 end
394 mov.cur.x = mov.cur.x + util.signum(vec[1]) * ff.force 468 -- if e:has_metadata() then
395 mov.cur.y = mov.cur.y - ff.force 469 -- local meta = e:get_metadata()
470 -- trace("Hit " .. meta.name .. " at position " .. pos1.x .. " " .. pos1.y)
471 -- else
472 -- trace("Hit entity at position " .. pos1.x .. " " .. pos1.y)
473 -- end
396 end 474 end
397 end 475 end
398 end 476 end
@@ -418,72 +496,68 @@ end
418 496
419local MovementSystem = System(Pos, Movement) 497local MovementSystem = System(Pos, Movement)
420function MovementSystem:exec(entity) 498function MovementSystem:exec(entity)
421 local pos = entity.get_component[Pos] 499 local rm, components = false, {}
422 local move = entity.get_component[Movement] 500
423 if entity.has_component[Input] then 501 local pos = entity:get_pos()
424 local input = entity.get_component[Input] 502 local move = entity:get_movement()
425 local left, right = input.raw[3], input.raw[4] 503 local status = Status()
426 -- local debug 504 if entity:has_status() then
427 -- if left then debug = " left, " else debug = " ____, " end 505 status = entity:get_status()
428 -- if right then debug = debug .. "right" else debug = debug .. "_____" end 506 end
429 if left and not right then 507 -- Horizontal movement
430 if move.dir then 508 local left, right = false, false
431 move.cur.x = 0 509 if not status:is_stunned() then
432 end 510 if entity:has_input() then
433 move.dir = false 511 local input = entity:get_input()
434 move.cur.x = math.max(-move.max.x, move.cur.x - move.accel.x) 512 left, right = input.raw[3], input.raw[4]
435 elseif right and not left then 513 elseif entity:has_movementai() then
436 if not move.dir then 514 if entity:has_target() then
437 move.cur.x = 0 515 local target = entity:get_target()
516 local delta = target.pos.x - pos.x
517 if -target.prox < delta and delta < target.prox and not target.sticky then
518 table.insert(components, target)
519 else
520 left, right = delta < 0, delta > 0
521 end
438 end 522 end
439 move.dir = true
440 move.cur.x = math.min(move.max.x, move.cur.x + move.accel.x)
441 else
442 -- TODO
443 end 523 end
444 -- horizontal movement 524 end
445 --move:update_speedx(input.raw[3], input.raw[4]) 525 if left and not right then
446 -- if input.raw[3] then 526 if move.dir then
447 -- if speed.right then 527 move.cur.x = 0
448 -- speed.cur.x = 0 528 end
449 -- end 529 move.dir = false
450 -- speed.right = false 530 move.cur.x = math.max(-move.max.x, move.cur.x - move.accel.x)
451 -- speed.cur.x = math.max(-speed.max, speed.cur.x - speed.accel) 531 elseif right and not left then
452 -- elseif input.raw[4] then 532 if not move.dir then
453 -- if not speed.right then 533 move.cur.x = 0
454 -- speed.cur.x = 0 534 end
455 -- end 535 move.dir = true
456 -- speed.right = true 536 move.cur.x = math.min(move.max.x, move.cur.x + move.accel.x)
457 -- speed.cur.x = math.min(speed.max, speed.cur.x + speed.accel) 537 end
458 -- end 538 -- Vertical movement
459 if entity.has_component[Jump] then 539 if entity:has_jump() and not status:is_stunned() then
460 local jump = entity.get_component[Jump] 540 local jump = entity:get_jump()
461 if jump.jumping then 541 local up = false
462 move.cur.y = move.cur.y + .2 542 if entity:has_input() then
463 if pos:on_ground() then 543 local input = entity:get_input()
464 jump.jumping = false 544 up = input.rawp[1]
465 end 545 end
466-- if jump.cur < jump.pwr then 546 if jump.jumping then
467-- jump.cur = jump.cur + 1 547 move.cur.y = move.cur.y + .2
468-- speed.cur.y = - (5 / jump.cur) 548 if pos:on_ground() then
469-- else 549 jump.jumping = false
470-- jump.jumping = false
471-- end
472 elseif input.rawp[1] and pos:on_ground() then
473 move.cur.y = -4
474 jump.jumping = true
475-- if jump.cur > 0 then
476-- jump.cur = jump.cur - 1
477-- elseif speed.on_ground then
478-- end
479 end 550 end
551 elseif up and pos:on_ground() then
552 move.cur.y = -4
553 jump.jumping = true
480 end 554 end
481 else
482 -- AI
483 end 555 end
556 -- Gravity
557 move.cur.y = math.min(2, move.cur.y + .2)
484 -- Horizontal friction 558 -- Horizontal friction
485 local friction = .1 559 local friction = .1
486 if pos:on_ground() then 560 if pos:on_ground(status:is_phantom()) then
487 friction = .2 561 friction = .2
488 end 562 end
489 if move.cur.x > 0 then 563 if move.cur.x > 0 then
@@ -491,25 +565,25 @@ function MovementSystem:exec(entity)
491 else 565 else
492 move.cur.x = math.min(0, move.cur.x + friction) 566 move.cur.x = math.min(0, move.cur.x + friction)
493 end 567 end
494 -- Gravity
495 move.cur.y = math.min(2, move.cur.y + .2)
496 -- -- collision check 568 -- -- collision check
497 local posx = pos.x + move.cur.x 569 local posx = pos.x + move.cur.x
498 local posy = pos.y + move.cur.y 570 local posy = pos.y + move.cur.y
499 -- -- movement on the X
500 local collx = false 571 local collx = false
501 for y = pos.y//8,(pos.y+7)//8 do
502 collx = collx or fget(mget(posx//8,y),0) or fget(mget((posx+7)//8,y),0)
503 end
504 -- movement on the Y
505 local colly = false 572 local colly = false
506 for x = pos.x//8,(pos.x+7)//8 do 573 if not status:is_phantom() then
507 colly = colly or fget(mget(x,posy//8),0) or fget(mget(x,(posy+7)//8),0) 574 -- -- movement on the X
575 for y = pos.y//8,(pos.y+7)//8 do
576 collx = collx or fget(mget(posx//8,y),0) or fget(mget((posx+7)//8,y),0)
577 end
578 -- movement on the Y
579 for x = pos.x//8,(pos.x+7)//8 do
580 colly = colly or fget(mget(x,posy//8),0) or fget(mget(x,(posy+7)//8),0)
581 end
582 -- local cx, cy
583 -- if collx then cx = "true" else cx = "false" end
584 -- if colly then cy = "true" else cy = "false" end
585 -- print("Collision: "..cx..", "..cy)
508 end 586 end
509 -- local cx, cy
510 -- if collx then cx = "true" else cx = "false" end
511 -- if colly then cy = "true" else cy = "false" end
512 -- print("Collision: "..cx..", "..cy)
513 if not collx then 587 if not collx then
514 pos.x = posx 588 pos.x = posx
515 end 589 end
@@ -519,6 +593,23 @@ function MovementSystem:exec(entity)
519 else 593 else
520 move.cur.y = 0 594 move.cur.y = 0
521 end 595 end
596
597 if rm then components = {} end
598 return rm or next(components), components -- next(table) used as a "not empty" check
599end
600
601local StatusSystem = System(Status)
602function StatusSystem:exec(entity)
603 entity:get_status():tick()
604end
605
606local BoundarySystem = System(Pos,Health)
607function BoundarySystem:exec(entity,lpos)
608 local health = entity:get_health()
609 if health.cur <= 0 then
610 local x, y = entity:get_pos():to_screen(lpos)
611 return x < 0 or y < 0 or x > util.screen.width or y > util.screen.height
612 end
522end 613end
523 614
524local DrawingSystem = System(Pos) 615local DrawingSystem = System(Pos)
@@ -617,6 +708,16 @@ function ParticlesSystem:exec(entity, game)
617 end 708 end
618end 709end
619 710
711local TargetSystem = System(Movement,MovementAI)
712function TargetSystem:exec(entity)
713 -- TODO to be removed
714 if not entity:has_target() then
715 local x, y = math.random(util.screen.width), math.random(util.screen.height)
716 trace("New target: "..x..", "..y)
717 entity:with(Target(Pos(x,y)))
718 end
719end
720
620local player = Entity():with({ 721local player = Entity():with({
621 Metadata("Giulia"), Sprite(256), 722 Metadata("Giulia"), Sprite(256),
622 Pos(100,10), Health(100), Status(), 723 Pos(100,10), Health(100), Status(),
@@ -663,13 +764,20 @@ function TIC()
663 cls(0) 764 cls(0)
664 765
665 InputSystem:run(game.entities) 766 InputSystem:run(game.entities)
767 TargetSystem:run(game.entities)
768
769 StatusSystem:run(game.entities)
770
666 MovementSystem:run(game.entities) 771 MovementSystem:run(game.entities)
667 ActionSystem:run(game.entities, game) 772 ActionSystem:run(game.entities, game)
668 EffectSystem:run(game.entities)
669 ForceFieldSystem:run(game.entities, game.entities) 773 ForceFieldSystem:run(game.entities, game.entities)
774
670 PosAnimSystem:run(game.entities) 775 PosAnimSystem:run(game.entities)
776 EffectSystem:run(game.entities)
671 ParticlesSystem:run(game.entities, game) 777 ParticlesSystem:run(game.entities, game)
778
672 LevelSystem:run(game.levels, game) 779 LevelSystem:run(game.levels, game)
780 BoundarySystem:run(game.entities, game.levels[game.level]:get_pos())
673 781
674 DrawingSystem:draw(game) 782 DrawingSystem:draw(game)
675 783