From d4c79731b0c4467dde2f6d262c614d2630008f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lera=20Elvo=C3=A9?= Date: Sat, 15 Feb 2025 14:58:27 +0300 Subject: [PATCH] duckie :) --- data/images/duckie.png | Bin 3534 -> 4383 bytes data/scripts/classes/duck.lua | 104 ++++++++++++++++++++++++++++++++++ data/scripts/classes/feed.lua | 1 + data/scripts/game.lua | 32 +++++++++-- data/scripts/types/list.lua | 98 ++++++++++++++++++++++++++++++++ data/scripts/util.lua | 8 +++ 6 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 data/scripts/classes/duck.lua create mode 100644 data/scripts/types/list.lua diff --git a/data/images/duckie.png b/data/images/duckie.png index 92ad8409aea74b80a6321a8cf28523c3f13d6425..91c826b0ead877b665f5c0cc70a842cb5611583a 100644 GIT binary patch literal 4383 zcmeHLYj6|S6<(Q_F~K+lW)dFtvJe88)jqV6r5y{5B^jx)Tw|+7fhlCQdnGSuwX(Fb zWv2m^JZzEaaz}2h*TB|4g~pE;ES-6{U+?qR;jEdV%@#ZO)2x-JS)So=*22>^ zp0NPh^welXI#bUF>4U&>pX_qg`CKkMo>0Sb6k%BU$i4fW-nS|j^=xgp)AL+$xTLwa z?&#ltO6)mPze%6F}If8}72X-dh>Rdqt6QhTsrVDZ+g zg#%xi&%QYL-jL&;XMWX_$+#CqtRF1Dh(Gzq)7y_+-R)_t=d#J{%%7OOgT1K_`^(?; z9Gdpz%EOO;S#i?4dVY8Ca;T9_W##NMPcM9glAP!4YV*0W)VbI0*eNVipI@Ck`ss=T z!C%jnymcGa-!}iF&fjm_7h2-KKC|>j=id(f=H~}a+*G}1THu(gd8lvaK=M zp1GBO?P*?fHZx#;f1CZ?t0((U*Jk=Gaik4?xSYQ3Ywzmv?9IHX^krY^diI_-{#;BC z1{!L59^J(~P-RN5?NZ+{xn8RL)8X@*?3O zd^|LS6-N_0<^_MS15z+82ZCDAU&D*4ViqJdgv@CruAdiHu{IqS#O+AKLntE0oW$qd zuM@Z|If;!{KkbjZP?PL!PoReO+JM-;U35sq+BHQLX&wj^qzQOhiN=zA+DRC^JlOg$ zMc@WR+wLTSkVY;wfpE^unQ79KmRne2O%Yy^kivYuyE;z+Pfnsq)8afuwYIjJTPyVlFw0m& zW2k(wq$b2fq*DR8Sq2^^EU~nm4wL1qjV3uOCy)-t5+*H<5Mu!jhLg}3iti<4$VwqP zHY%M;0xB*n2{us-k&=P3{v85yE+7lbIv@)q2EGtOI#{cNwAc|FvarZz z3vuJwlWJIN6%uG&1at(gAU%y=Q1}YtpjM7Yw>BaD6hJVNW=V$oPr<1ChAF*f4DYyt z`Y%l?48WKo1Ae(NC|*ztsgYus*Gw-v-{L2K7T@9uKs~w0g!G-1Yf`QWDKH`MWOhx; zH6aBi1fI;U|C?Mzqpwpa27d*$!pltNWvzMVW97;%y;~{|Em-*CUBCOX=BWzP+>_41 z!JoZZUAbcO{n2Gxlow_!nTlhk!ph94qb+SubiZ9&Hm7h2W;&xR`OCq_9{oqAWG%Zx z!nMlYYj@Vehg_)tlbiyQ%k6)3HUb?h7vcy8rZNrK?L;ovrN%Hr=+k sLk$(A+Kx5t)%Krnx;nLL>)XXEZ``q}^hXi>pB$LaQ|o@A>h4|t21zpDJOBUy literal 3534 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 IDLE_TIME then + self.state = States.WANDER + self.wander_timer = 0 + self.direction = Vector3(0, 0, -1):rotated(Vector3.UP, math.random() * (math.pi * 2.0)) + return + end +end + +function Duck:wander(delta) + self.wander_timer = self.wander_timer + delta + if self.wander_timer >= WANDER_TIME then + self.state = States.IDLE + self.wander_timer = 0 + return + end + + local target_vel = self.direction * self.speed + self.velocity = self.velocity:lerp(target_vel, self.accel) + self.position = self.position + self.velocity +end + +function Duck:chase(delta) + local hpos = self.position:horizontal() + local fhpos = self.target.position:horizontal() + + local dist = hpos:distance_to(fhpos) + if dist <= 0.1 then + self.AteFeed:emit(self.target) + self.wander_timer = 0 + self.state = States.IDLE + self.target = nil + else + local dir = self.position:horizontal():direction_to(self.target.position:horizontal()) + local target_vel = dir * self.chase_speed + self.velocity = self.velocity:lerp(target_vel, self.accel) + self.position = self.position + self.velocity + end +end + +function Duck:start_chase(feed) + if self.state == States.CHASE then return end + self.state = States.CHASE + self.target = feed + feed.occupied = true +end + +return Duck \ No newline at end of file diff --git a/data/scripts/classes/feed.lua b/data/scripts/classes/feed.lua index b4cd07f..0a13428 100644 --- a/data/scripts/classes/feed.lua +++ b/data/scripts/classes/feed.lua @@ -7,6 +7,7 @@ local Feed = { direction = {}, velocity = {}, landed = false, + occupied = false, speed = 0.15, } diff --git a/data/scripts/game.lua b/data/scripts/game.lua index ca4b477..2b38371 100644 --- a/data/scripts/game.lua +++ b/data/scripts/game.lua @@ -1,12 +1,29 @@ util = require "util" local player = require "classes.player" local Vector3 = require "types.vector3" +local List = require "types.list" local Feed = require "classes.feed" -local feed = {} +local feed = List() + +local Duck = require "classes.duck" +local ducks = List{Duck.new(Vector3(0, 0.4, 3))} local function create_feed(position, direction) - table.insert(feed, Feed.new(position, direction)) + local f = Feed.new(position, direction) + feed:push(f) + local eligible_ducks = ducks:filter( + function (duck) + return duck.state ~= Duck.STATES.CHASE + end + ) + if #eligible_ducks == 0 then return end + eligible_ducks[1]:start_chase(f) + f.occupied = true +end + +local function delete_feed(f) + util.list_remove_value(feed, f) end -- called every frame, with constant delta time @@ -18,16 +35,23 @@ function game_tick() capture = false, } player.ThrowPressed:connect(create_feed) + for _, v in ipairs(ducks) do + v.AteFeed:connect(delete_feed) + end end ctx.mouse_capture = ctx.udata.capture input_action{name = "toggle_mouse", control = "ESCAPE"} - + if input_action_just_pressed{name = "toggle_mouse"} then ctx.udata.capture = not ctx.udata.capture end - for _, v in pairs(feed) do + for _, v in ipairs(feed) do + v:tick(ctx) + end + + for _, v in ipairs(ducks) do v:tick(ctx) end diff --git a/data/scripts/types/list.lua b/data/scripts/types/list.lua new file mode 100644 index 0000000..219bb69 --- /dev/null +++ b/data/scripts/types/list.lua @@ -0,0 +1,98 @@ +---@class List +local List = {} + +local function reduce(list, f, init) + local acc = init + for i, v in ipairs(list) do + acc = f(v, acc, i) + end + return acc +end + +local function filter(list, predicate) + return reduce(list, function(el, acc, i) + if predicate(el) then + table.insert(acc, el) + end + return acc + end, List.create{}) +end + +local function shallow_copy(t) + local t2 = {} + for k, v in ipairs(t) do + t2[k] = v + end + return t2 +end + +List.__index = List +setmetatable(List, { + __call = function(self, ...) + local args = {...} + if #args == 0 then + return List.create() + end + + if type(args[1]) == "table" then + return List.create(args[1]) + end + return List.create(args) + end +}) + +---Constructs a new list from the given table. +---@param from table? +---@return List +function List.create(from) + from = from or {} + local l = shallow_copy(from) + return setmetatable(l, List) +end + +function List:__tostring() + local s = "List(" .. table.concat(self, ", ", 1, #self) .. ")" + return s +end + +---Appends v to the end of the list. +---@param v any +function List:push(v) + table.insert(self, v) +end + +---Removes the last element in the list and returns it. +---@return any +function List:pop() + return table.remove(self, #self) +end + +---Reduce. +---@param f function called with element, accumulator, index +---@param init any initial value of accumulator +---@return any +function List:reduce(f, init) + return reduce(self, f, init) +end + +---Returns a new List of all elements of this list that match the predicate function. +---@param predicate function called with element +---@return List +function List:filter(predicate) + return filter(self, predicate) +end + +---Returns the index of value, if it exists in the list, -1 otherwise. +---@param value any +---@return integer +function List:find(value) + local idx = -1 + for i, v in ipairs(self) do + if v == value then + idx = i + break + end + end + return idx +end +return List \ No newline at end of file diff --git a/data/scripts/util.lua b/data/scripts/util.lua index 1cad958..a4497fc 100644 --- a/data/scripts/util.lua +++ b/data/scripts/util.lua @@ -116,5 +116,13 @@ function util.clamp(v, min, max) return math.max(math.min(v, max), min) end +---returns a random float in range (min, max) +---@param min number +---@param max number +---@return number +function util.random_float(min, max) + return min + math.random() * (max - min) +end + return util \ No newline at end of file