diff --git a/VERSION b/VERSION index ed63cdf..81de5c5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.9 \ No newline at end of file +0.3.10 \ No newline at end of file diff --git a/init.lua b/init.lua index d1ec9cc..661dc85 100755 --- a/init.lua +++ b/init.lua @@ -12,42 +12,78 @@ dofile(minetest.get_modpath(minetest.get_current_modname()).."/config.lua") dofile(minetest.get_modpath(minetest.get_current_modname()).."/init_hudbars.lua") dofile(minetest.get_modpath(minetest.get_current_modname()).."/lib_savetable.lua") dofile(minetest.get_modpath(minetest.get_current_modname()).."/lib_file_exists.lua") +dofile(minetest.get_modpath(minetest.get_current_modname()).."/lib_eventemitter.lua") +minetest_wadsprint.api.events = EventEmitter:new() --- API function to safely get player's stamina value. -function minetest_wadsprint.api.get_stamina_rate(player_name) - if minetest_wadsprint.players[player_name] ~= nil then - return minetest_wadsprint.players[player_name].stamina / minetest_wadsprint.STAMINA_MAX_VALUE - end +function minetest_wadsprint.api.stats(player_name) + local player = minetest_wadsprint.players[player_name] + if player ~= nil then + return + { + name = player_name, + stamina = player.stamina, + is_sprinting = player.is_sprinting, + is_ready_to_sprint = player.is_ready_to_sprint, + is_sprinting_physics_on = player.is_sprinting_physics_on, + } + end end --- API function to safely change player's stamina. -function minetest_wadsprint.api.change_stamina_by_coefficient(player_name,stamina_change_coefficient) - if minetest_wadsprint.players[player_name] ~= nil then - local player = minetest_wadsprint.players[player_name] - player.stamina = player.stamina + (stamina_change_coefficient * minetest_wadsprint.STAMINA_MAX_VALUE) - if player.stamina < 0 then - player.stamina = 0 - elseif player.stamina > minetest_wadsprint.STAMINA_MAX_VALUE then - player.stamina = minetest_wadsprint.STAMINA_MAX_VALUE +function minetest_wadsprint.api.stamina(player_name,stamina_rate) + local player = minetest_wadsprint.players[player_name] + if player ~= nil then + if stamina_value ~= nil then + minetest_wadsprint.set_stamina(player, minetest_wadsprint.STAMINA_MAX_VALUE * stamina_value) + else + return player.stamina / minetest_wadsprint.STAMINA_MAX_VALUE end - minetest_wadsprint.hudbar_update_stamina(player) - end - return minetest_wadsprint.api + end +end + +function minetest_wadsprint.api.addstamina(player_name,stamina_rate_change) + local player = minetest_wadsprint.players[player_name] + if player ~= nil then + minetest_wadsprint.set_stamina(player, player.stamina + minetest_wadsprint.STAMINA_MAX_VALUE * stamina_value) + end end function minetest_wadsprint.stamina_update_cycle(player) if player.is_sprinting then - player.stamina = player.stamina - (minetest_wadsprint.STAMINA_MAX_VALUE * minetest_wadsprint.SPRINT_STAMINA_DECREASE_PER_UPDATE_PERIOD_COEFFICIENT) - if player.stamina < 0 then - minetest_wadsprint.set_sprinting(player,false) - minetest_wadsprint.set_ready_to_sprint(player,false) - player.stamina = 0 - end + minetest_wadsprint.set_stamina(player, player.stamina - (minetest_wadsprint.STAMINA_MAX_VALUE * minetest_wadsprint.SPRINT_STAMINA_DECREASE_PER_UPDATE_PERIOD_COEFFICIENT)) elseif player.stamina < minetest_wadsprint.STAMINA_MAX_VALUE then - player.stamina = player.stamina + (minetest_wadsprint.STAMINA_MAX_VALUE * minetest_wadsprint.SPRINT_STAMINA_INCREASE_PER_UPDATE_PERIOD_COEFFICIENT) - if player.stamina > minetest_wadsprint.STAMINA_MAX_VALUE then - player.stamina = minetest_wadsprint.STAMINA_MAX_VALUE - end + minetest_wadsprint.set_stamina(player, player.stamina + (minetest_wadsprint.STAMINA_MAX_VALUE * minetest_wadsprint.SPRINT_STAMINA_INCREASE_PER_UPDATE_PERIOD_COEFFICIENT)) + end +end + +function minetest_wadsprint.set_stamina(player,stamina_value) + local old_stamina_value = player.stamina + if stamina_value < 0 then + minetest_wadsprint.set_sprinting(player,false) + minetest_wadsprint.set_ready_to_sprint(player,false) + player.stamina = 0 + elseif stamina_value > minetest_wadsprint.STAMINA_MAX_VALUE then + player.stamina = minetest_wadsprint.STAMINA_MAX_VALUE + else + player.stamina = stamina_value + end + if old_stamina_value >= minetest_wadsprint.DYSPNEA_THRESHOLD_VALUE and player.stamina < minetest_wadsprint.DYSPNEA_THRESHOLD_VALUE then + minetest_wadsprint.api.events:emit( + "dyspnea", + { + player = player, + value = true, + name = "dyspnea", + } + ) + elseif old_stamina_value < minetest_wadsprint.DYSPNEA_THRESHOLD_VALUE and player.stamina >= minetest_wadsprint.DYSPNEA_THRESHOLD_VALUE then + minetest_wadsprint.api.events:emit( + "dyspnea", + { + player = player, + value = false, + name = "dyspnea", + } + ) end minetest_wadsprint.hudbar_update_stamina(player) end @@ -74,6 +110,9 @@ function minetest_wadsprint.set_sprinting_physics(player,is_on) end end +-- If player.is_sprinting that means he is actually moving forward. If player is not moving then he +-- isn't sprinting. `player.is_sprinting` could be nil if the value is not initialized. Nil is not +-- equal nor to true neither to false. function minetest_wadsprint.set_sprinting(player,is_sprinting) if player.is_sprinting ~= is_sprinting then if player.is_sprinting ~= nil then @@ -91,6 +130,13 @@ function minetest_wadsprint.set_sprinting(player,is_sprinting) end end +-- Main use of this function is to put player in a state when pressing "W" would trigger the +-- set_sprinting function thus you won't need to hold "A"+"D" to keep sprinting. Also it alters player +-- physics to workaround lag between pressing "W" and actual set_sprinting call. So if player is ready +-- to sprint it is sure that his physics is already in sprinting state and he can not afraid to fall +-- while jumping from a tree to tree just because the lag between pressing "W" and set_sprinting call +-- would be too big. At the same time being only ready to sprint and not actually sprinting does not +-- decreases the stamina because decreasing stamina for nothing is unfair. function minetest_wadsprint.set_ready_to_sprint(player,is_ready_to_sprint) if player.is_ready_to_sprint ~= is_ready_to_sprint then if is_ready_to_sprint then @@ -124,7 +170,7 @@ end function minetest_wadsprint.reset_stamina(player,stamina_value) if stamina_value == nil then stamina_value = minetest_wadsprint.STAMINA_MAX_VALUE end - player.stamina = stamina_value + minetest_wadsprint.set_stamina(player,stamina_value) minetest_wadsprint.set_sprinting(player,false) minetest_wadsprint.set_ready_to_sprint(player,false) return player @@ -168,9 +214,20 @@ minetest.register_on_joinplayer(function(player_obj) player = { stamina = minetest_wadsprint.STAMINA_MAX_VALUE } end player.obj = player_obj + player.name = playername minetest_wadsprint.players[playername] = player minetest_wadsprint.initialize_hudbar(player) minetest_wadsprint.reset_stamina(player,player.stamina) + if player.stamina < minetest_wadsprint.DYSPNEA_THRESHOLD_VALUE then + minetest_wadsprint.api.events:emit( + "dyspnea", + { + player = player, + value = true, + name = "dyspnea", + } + ) + end end) minetest.register_on_respawnplayer(function(player_obj) diff --git a/lib_eventemitter.lua b/lib_eventemitter.lua new file mode 100644 index 0000000..59bdd19 --- /dev/null +++ b/lib_eventemitter.lua @@ -0,0 +1,185 @@ +------------------------------------------------------------------------------ +-- EventEmitter Class in Node.js Style +-- LICENSE: MIT +-- Simen Li +------------------------------------------------------------------------------ +EventEmitter = { defaultMaxListeners = 10 } +local PFX = '__lsn_' +local PFX_LEN = #PFX + +setmetatable(EventEmitter, { + __call = function (_, ...) return EventEmitter:new(...) end +}) + +local function rmEntry(tbl, pred) + local x, len = 0, #tbl + for i = 1, len do + local trusy, idx = false, (i - x) + if (type(pred) == 'function') then trusy = pred(tbl[idx]) + else trusy = tbl[idx] == pred + end + + if (tbl[idx] ~= nil and trusy) then + tbl[idx] = nil + table.remove(tbl, idx) + x = x + 1 + end + end + return tbl +end + +function EventEmitter:new(obj) + obj = obj or {} + self.__index = self + setmetatable(obj, self) + obj._on = {} + + return obj +end + +function EventEmitter:evTable(ev) + if (type(self._on[ev]) ~= 'table') then self._on[ev] = {} end + return self._on[ev] +end + +function EventEmitter:getEvTable(ev) + return self._on[ev] +end + +-- ************************************************************************ -- +-- ** Public APIs * -- +-- ************************************************************************ -- +function EventEmitter:addListener(ev, listener) + local pfx_ev = PFX .. tostring(ev) + local evtbl = self:evTable(pfx_ev) + local maxLsnNum = self.currentMaxListeners or self.defaultMaxListeners + local lsnNum = self:listenerCount(ev) + table.insert(evtbl, listener) + + if (lsnNum > maxLsnNum) then print('WARN: Number of ' .. string.sub(pfx_ev, PFX_LEN + 1) .. " event listeners: " .. tostring(lsnNum)) end + return self +end + +function EventEmitter:emit(ev, ...) + local pfx_ev = PFX .. tostring(ev) + local evtbl = self:getEvTable(pfx_ev) + if (evtbl ~= nil) then + for _, lsn in ipairs(evtbl) do + local status, err = pcall(lsn, ...) + if not (status) then print(string.sub(_, PFX_LEN + 1) .. " emit error: " .. tostring(err)) end + end + end + + -- one-time listener + pfx_ev = pfx_ev .. ':once' + evtbl = self:getEvTable(pfx_ev) + + if (evtbl ~= nil) then + for _, lsn in ipairs(evtbl) do + local status, err = pcall(lsn, ...) + if not (status) then print(string.sub(_, PFX_LEN + 1) .. " emit error: " .. tostring(err)) end + end + + rmEntry(evtbl, function (v) return v ~= nil end) + self._on[pfx_ev] = nil + end + return self +end + +function EventEmitter:getMaxListeners() + return self.currentMaxListeners or self.defaultMaxListeners +end + +function EventEmitter:listenerCount(ev) + local totalNum = 0 + local pfx_ev = PFX .. tostring(ev) + local evtbl = self:getEvTable(pfx_ev) + + if (evtbl ~= nil) then totalNum = totalNum + #evtbl end + + pfx_ev = pfx_ev .. ':once' + evtbl = self:getEvTable(pfx_ev) + + if (evtbl ~= nil) then totalNum = totalNum + #evtbl end + + return totalNum +end + +function EventEmitter:listeners(ev) + local pfx_ev = PFX .. tostring(ev) + local evtbl = self:getEvTable(pfx_ev) + local clone = {} + + if (evtbl ~= nil) then + for i, lsn in ipairs(evtbl) do table.insert(clone, lsn) end + end + + pfx_ev = pfx_ev .. ':once' + evtbl = self:getEvTable(pfx_ev) + + if (evtbl ~= nil) then + for i, lsn in ipairs(evtbl) do table.insert(clone, lsn) end + end + + return clone +end + +EventEmitter.on = EventEmitter.addListener + +function EventEmitter:once(ev, listener) + local pfx_ev = PFX .. tostring(ev) .. ':once' + local evtbl = self:evTable(pfx_ev) + local maxLsnNum = self.currentMaxListeners or self.defaultMaxListeners + local lsnNum = self:listenerCount(ev) + if (lsnNum > maxLsnNum) then print('WARN: Number of ' .. ev .. " event listeners: " .. tostring(lsnNum)) end + + table.insert(evtbl, listener) + return self +end + +function EventEmitter:removeAllListeners(ev) + if ev ~= nil then + local pfx_ev = PFX .. tostring(ev) + local evtbl = self:evTable(pfx_ev) + rmEntry(evtbl, function (v) return v ~= nil end) + + pfx_ev = pfx_ev .. ':once' + evtbl = self:evTable(pfx_ev) + rmEntry(evtbl, function (v) return v ~= nil end) + self._on[pfx_ev] = nil + else + for _pfx_ev, _t in pairs(self._on) do self:removeAllListeners(string.sub(_pfx_ev, PFX_LEN + 1)) end + end + + for _pfx_ev, _t in pairs(self._on) do + if (#_t == 0) then self._on[_pfx_ev] = nil end + end + + return self +end + +function EventEmitter:removeListener(ev, listener) + local pfx_ev = PFX .. tostring(ev) + local evtbl = self:evTable(pfx_ev) + local lsnCount = 0 + assert(listener ~= nil, "listener is nil") + -- normal listener + rmEntry(evtbl, listener) + + if (#evtbl == 0) then self._on[pfx_ev] = nil end + + -- emit-once listener + pfx_ev = pfx_ev .. ':once' + evtbl = self:evTable(pfx_ev) + rmEntry(evtbl, listener) + + if (#evtbl == 0) then self._on[pfx_ev] = nil end + return self +end + +function EventEmitter:setMaxListeners(n) + self.currentMaxListeners = n + return self +end + +return EventEmitter diff --git a/util/minetest_wadsprint_version.png b/util/minetest_wadsprint_version.png index e9d0b52..c79808e 100644 Binary files a/util/minetest_wadsprint_version.png and b/util/minetest_wadsprint_version.png differ