diff --git a/maps/includes/base.lua b/maps/includes/base.lua index c73e3f9..b33f1ad 100644 --- a/maps/includes/base.lua +++ b/maps/includes/base.lua @@ -7,7 +7,9 @@ -- Do not change this file. ----------------------------------------------------------------------------- +require "util.utils" Class = require "util.class" +Collection = require "util.collection" ----------------------------------------------------------------------------- -- defines @@ -48,6 +50,47 @@ function baseclass:new (o) end +----------------------------------------------------------------------------- +-- set up pairs and ipairs for iterating the global entity list +-- +-- Example usage: +-- +-- for ent_id, ent in pairs(GlobalEntityList) do +-- print(ent_id, ent) +-- end +-- +-- Note: The order of iteration is always arbitrary +----------------------------------------------------------------------------- +local GlobalEntityListIterator = function() + local entity = GlobalEntityList:FirstEntity() + return function() + local cur_ent = entity + entity = GlobalEntityList:NextEntity(cur_ent) + if cur_ent then + return cur_ent:GetId(), cur_ent + else + return nil + end + end +end + +local ipairs_base = ipairs +ipairs = function(t) + if t == GlobalEntityList then + return GlobalEntityListIterator() + end + return ipairs_base(t) +end + +local pairs_base = pairs +pairs = function(t) + if t == GlobalEntityList then + return GlobalEntityListIterator() + end + return pairs_base(t) +end + + ----------------------------------------------------------------------------- -- make luabind's class_info function safer -- (don't crash if class_info() is called on non-luabind objects) diff --git a/maps/includes/util/collection.lua b/maps/includes/util/collection.lua new file mode 100644 index 0000000..ea39e02 --- /dev/null +++ b/maps/includes/util/collection.lua @@ -0,0 +1,228 @@ +-- backwards compatibility for the Collection class + +local Class = require "util.class" +require "util.utils" + +local function setup_items_key(self) + -- keep a usable reference to the base metatable + local real_obj = setmetatable({}, getmetatable(self)) + -- add special handling of the items key + setmetatable(self, { + -- the items key returns an iterator + __index = function(t, k) + if k == "items" then + local i = 0 + local n = self:Count() + return function() + i = i + 1 + if i <= n then return self.entities[i] end + end + end + return real_obj[k] + end, + -- protect the items key from being assigned + __newindex = function(t, k, v) + if k == "items" then + return + end + real_obj[k] = v + end + }) +end + +local function get_param_as_table(param) + if param == nil then return {} end + if type(param) == "table" then return param end + return {param} +end + +-- lua tables start at index 1 +local function to_lua_index(collection_index) + return collection_index+1 +end + +-- Collection started at index 0 +local function to_collection_index(lua_index) + return lua_index-1 +end + +local Collection = Class(function(self, entity_or_entities) + setup_items_key(self) + self.entities = get_param_as_table(entity_or_entities) +end) + +Collection.filters = { + [CF.kNone] = function(entity) return true end, + [CF.kPlayers] = function(entity) return IsPlayer(entity) end, + [CF.kHumanPlayers] = function(entity) return IsPlayer(entity) and not CastToPlayer(entity):IsBot() end, + [CF.kBotPlayers] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):IsBot() end, + [CF.kPlayerScout] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kScout end, + [CF.kPlayerSniper] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kSniper end, + [CF.kPlayerSoldier] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kSoldier end, + [CF.kPlayerDemoman] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kDemoman end, + [CF.kPlayerMedic] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kMedic end, + [CF.kPlayerHWGuy] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kHwguy end, + [CF.kPlayerPyro] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kPyro end, + [CF.kPlayerSpy] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kSpy end, + [CF.kPlayerEngineer] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kEngineer end, + [CF.kPlayerCivilian] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kCivilian end, + [CF.kPlayerScout] = function(entity) return IsPlayer(entity) and CastToPlayer(entity):GetClass() == Player.kScout end, + [CF.kTeams] = function(entity) return IsEntity(entity) and entity:GetTeamId() >= Team.kSpectator and entity:GetTeamId() <= Team.kGreen end, + [CF.kTeamSpec] = function(entity) return IsEntity(entity) and entity:GetTeamId() == Team.kSpectator end, + [CF.kTeamBlue] = function(entity) return IsEntity(entity) and entity:GetTeamId() == Team.kBlue end, + [CF.kTeamRed] = function(entity) return IsEntity(entity) and entity:GetTeamId() == Team.kRed end, + [CF.kTeamYellow] = function(entity) return IsEntity(entity) and entity:GetTeamId() == Team.kYellow end, + [CF.kTeamGreen] = function(entity) return IsEntity(entity) and entity:GetTeamId() == Team.kGreen end, + [CF.kProjectiles] = function(entity) return IsProjectile(entity) end, + [CF.kGrenades] = function(entity) return IsGrenade(entity) end, + [CF.kInfoScipts] = function(entity) return IsInfoScript(entity) end, + [CF.kInfoScript_Carried] = function(entity) return IsInfoScript(entity) and CastToInfoScript(entity):IsCarried() end, + [CF.kInfoScript_Dropped] = function(entity) return IsInfoScript(entity) and CastToInfoScript(entity):IsDropped() end, + [CF.kInfoScript_Returned] = function(entity) return IsInfoScript(entity) and CastToInfoScript(entity):IsReturned() end, + [CF.kInfoScript_Active] = function(entity) return IsInfoScript(entity) and CastToInfoScript(entity):IsActive() end, + [CF.kInfoScript_Inactive] = function(entity) return IsInfoScript(entity) and CastToInfoScript(entity):IsInactive() end, + [CF.kInfoScript_Removed] = function(entity) return IsInfoScript(entity) and CastToInfoScript(entity):IsRemoved() end, + [CF.kTraceBlockWalls] = function(entity) return true end, -- not applicable for this type of filtering + [CF.kBuildables] = function(entity) return IsBuildable(entity) end, + [CF.kDispenser] = function(entity) return IsDispenser(entity) end, + [CF.kSentrygun] = function(entity) return IsSentrygun(entity) end, + [CF.kDetpack] = function(entity) return IsDetpack(entity) end, + [CF.kJumpPad] = function(entity) return IsJumpPad(entity) end, +} + +function Collection.PassesFilter(entity, filter) + if not filter then filter = CF.kNone end + assert(Collection.filters[filter], "Unknown filter: "..tostring(filter)) + return Collection.filters[filter](entity) +end + +function Collection.PassesFilters(entity, filters) + if not filters then return true end + filters = get_param_as_table(filters) + for _,filter in ipairs(filters) do + if not Collection.PassesFilter(entity, filter) then + return false + end + end + return true +end + +function Collection:AddItem(entity_or_entities) + local entities_to_add = get_param_as_table(entity_or_entities) + + for i,entity_to_add in ipairs(entities_to_add) do + table.insert(self.entities, entity_to_add) + end +end + +function Collection:AddFiltered(entity_or_entities, filters) + local entities_to_add = get_param_as_table(entity_or_entities) + + for i,entity_to_add in ipairs(entities_to_add) do + if Collection.PassesFilters(entity_to_add, filters) then + table.insert(self.entities, entity_to_add) + end + end +end + +function Collection:RemoveItem(entity_or_entities) + local entities_to_find = get_param_as_table(entity_or_entities) + + for i,entity_to_find in ipairs(entities_to_find) do + local i = self:FindItemIndex(entity_to_find) + if i then + table.remove(self.entities, to_lua_index(i)) + end + end +end + +function Collection:RemoveAllItems() + table.clear(self.entities) +end + +function Collection:Count() + return #self.entities +end + +function Collection:NumItems() + return self:Count() +end + +function Collection:IsEmpty() + return self:Count() == 0 +end + +function Collection:HasItem(entity_or_entities) + local entities_to_find = get_param_as_table(entity_or_entities) + + for i,entity_to_find in ipairs(entities_to_find) do + if self:FindItemIndex(entity_to_find) then + return true + end + end + return false +end + +-- this is a strange function +function Collection:GetItem(entity_or_entities) + local entities_to_find = get_param_as_table(entity_or_entities) + + for i,entity_to_find in ipairs(entities_to_find) do + local i = self:FindItemIndex(entity_to_find) + if i then + return self:Element(i) + end + end +end + +function Collection:FindItemIndex(entity_to_find) + for i,entity in ipairs(self.entities) do + if entity:GetId() == entity_to_find:GetId() then + return to_collection_index(i) + end + end + return nil +end + +function Collection:Element(i) + return self.entities[to_lua_index(i)] +end + +function Collection:GetByFilter(filters) + filters = get_param_as_table(filters) + + -- optimization for players + local players_only = false + for _,filter in ipairs(filters) do + if filter >= CF.kPlayers and filter <= CF.kPlayerScout then + players_only = true + break + end + end + + if players_only then + self:AddFiltered(GetPlayers(), filters) + else + for ent_id, entity in ipairs(GlobalEntityList) do + self:AddFiltered(entity, filters) + end + end +end + +function Collection:GetByName(name_or_names, filters) + local names_to_find = get_param_as_table(name_or_names) + filters = get_param_as_table(filters) + + for i,name_to_find in ipairs(names_to_find) do + self:AddFiltered(GetEntitiesByName(name_to_find), filters) + end +end + +function Collection:GetInSphere(entity_or_origin, radius, filters) + filters = get_param_as_table(filters) + local origin = IsEntity(entity_or_origin) and entity_or_origin:GetOrigin() or entity_or_origin + local ignore_walls = not table.contains(filters, CF.kTraceBlockWalls) + self:AddFiltered(GetEntitiesInSphere(origin, radius, ignore_walls), filters) +end + +return Collection \ No newline at end of file diff --git a/maps/includes/util/utils.lua b/maps/includes/util/utils.lua new file mode 100644 index 0000000..d279d6c --- /dev/null +++ b/maps/includes/util/utils.lua @@ -0,0 +1,28 @@ + +function table.clear(tbl) + for k in pairs(tbl) do + tbl[k] = nil + end +end + +function table.contains(tbl, element) + if tbl == nil then return false end + for _, value in pairs(tbl) do + if value == element then + return true + end + end + return false +end + +function table.contains_any(tbl, elements) + if tbl == nil or elements == nil then return false end + for _, value in pairs(tbl) do + for _, element in pairs(elements) do + if value == element then + return true + end + end + end + return false +end \ No newline at end of file