Skip to content

Instantly share code, notes, and snippets.

@dbjorkholm
Last active September 4, 2016 18:34
Show Gist options
  • Save dbjorkholm/ce69140315b779dde694 to your computer and use it in GitHub Desktop.
Save dbjorkholm/ce69140315b779dde694 to your computer and use it in GitHub Desktop.
[TFS 1.x] Paintball Event
function onThink(interval, lastExecution, thinkInterval)
if #Game.getPlayers() < PaintballEvent.minPlayers or PaintballEvent.startingUp or PaintballEvent.running then
return true
end
PaintballEvent:init()
return true
end
local config = {
minutesToEnd = 5,
minutesToStart = 10,
rewards = {
{itemId = 2160, count = 5},
{itemId = 2160, count = 4},
{itemId = 2160, count = 3},
{itemId = 2160, count = 2},
{itemId = 2160, count = 1},
},
spawnArea = {fromPosition = Position(1000, 1000, 7), toPosition = Position(1000, 1000, 7)},
waitingRoomArea = {fromPosition = Position(1000, 1000, 7), toPosition = Position(1000, 1000, 7)},
teleport = {
enabled = false,
position = Position(1000, 1000, 7),
actionId = 1000,
},
ammunition = {
infinite = false,
refillOnDeath = true,
onStart = 100,
eachPoint = 2,
}
bullets = {
effect = CONST_ANI_SNOWBALL,
exhaustion = 5, -- Exhaustion in seconds
speed = 150, -- Speed in milliseconds
maxDistance = 6, -- Max distance a bullet can travel
},
points = {
removeOnDeath = true,
removePoints = 1,
onKill = 3,
}
}
local function getRandomSpawnPosition(fromPosition, toPosition)
local random = math.random
return Position(random(fromPosition.x, toPosition.x), random(fromPosition.y, toPosition.y), random(fromPosition.z, toPosition.z))
end
local function isInRange(pos, fromPos, toPos)
return pos.x >= fromPos.x and pos.y >= fromPos.y and pos.z >= fromPos.z and pos.x <= toPos.x and pos.y <= toPos.y and pos.z <= toPos.z
end
local function getNumberOrdinal(number)
if number >= 11 and number <= 19 then
return "th"
end
local number = number % 10
local ordinals = {"st", "nd", "rd"}
return ordinals[number] or "th"
end
local function blockProjectile(position)
local tile = Tile(position)
if not tile then
return false
end
local ground = tile:getGround()
if not ground or ground:hasProperty(CONST_PROP_BLOCKSOLID) then
return false
end
local items = tile:getItems()
for _, item in ipairs(items) do
local itemType = item:getType()
if itemType:getType() ~= ITEM_TYPE_MAGICFIELD and not itemType:isMovable() and item:hasProperty(CONST_PROP_BLOCKPROJECTILE) then
return true
end
end
return false
end
function ItemType.getNameDescription(self, count)
local count = count or 1
local description = ""
if self:isRune() then
description = string.format("%s %s (%d charges)", self:getArticle(), self:getName(), count)
elseif self:isStackable() and count > 1 then
description = string.format("%d %s", count, self:getPluralName())
elseif self:getArticle() ~= '' then
description = string.format("%s %s", self:getArticle(), self:getName())
else
description = self:getName()
end
return description
end
if not PaintballEvent then
PaintballEvent = {
players = { },
running = false,
startingUp = false,
minPlayers = 10,
eventStorage = 1234,
ammoStorage = 1235,
pointsStorage = 1236,
exhaustStorage = 1237,
endEvent = nil,
}
end
function PaintballEvent.addPlayer(self, player)
if not self.startingUp or self.running then
return
end
-- Send Message
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have joined the Paintball Event!")
-- Add Storages
player:setStorageValue(self.eventStorage, 1)
player:setStorageValue(self.ammoStorage, 100)
player:setStorageValue(self.pointsStorage, 0)
player:setStorageValue(self.exhaustStorage, 0)
-- Teleport Player
local waitingRoomPosition = getRandomSpawnPosition(config.waitingRoomArea.fromPosition, config.waitingRoomArea.toPosition)
player:getPosition():sendMagicEffect(CONST_ME_POFF)
player:teleportTo(waitingRoomPosition)
waitingRoomPosition:sendMagicEffect(CONST_ME_TELEPORT)
self.players[#self.players + 1] = player:getId()
end
function PaintballEvent.addReward(self, playerId, scoreId)
local reward = config.rewards[scoreId]
if not reward then
return
end
local player = Player(playerId)
if not player or not player:addItem(reward.itemId, reward.count) then
return
end
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, string.format('You received %s for coming in %d%s place.', ItemType(reward.itemId):getNameDescription(reward.count), scoreId, getNumberOrdinal(scoreId)))
end
function PaintballEvent.broadcastMessage(self, type, message)
if not self.running or #self.players == 0 then
return
end
for i = 1, #self.players do
local tmpPlayer = Player(self.players[i])
if tmpPlayer then
tmpPlayer:sendTextMessage(type, message)
end
end
end
function PaintballEvent.getTopScorers(self)
local scorers = { }
if #self.players ~= 0 then
for i = 1, #self.players do
local tmpPlayer = Player(self.players[i])
if tmpPlayer then
scorers[#scorers + 1] = {id = tmpPlayer:getId(), name = tmpPlayer:getName(), points = math.max(0, tmpPlayer:getStorageValue(self.pointsStorage))}
end
end
end
table.sort(scorers, function(a, b) return a.points > b.points end)
for i = 1, #scorers do
scorers[#scorers] = nil
if #scorers == #config.rewards then
break
end
end
return scorers
end
function PaintballEvent.init(self)
if self.startingUp or self.running then
return
end
for i = 0, config.minutesToStart do
if i == config.minutesToStart then
addEvent(function(self) self:start() end, i * 60000, self)
else
addEvent(Game.broadcastMessage, i * 60000, string.format('The Paintball Event will start in %d minute%s. You can join the event by typing !paintball join.', config.minutesToStart - i, config.minutesToStart - i ~= 1 and "s" or ""), MESSAGE_EVENT_ADVANCE)
end
end
-- Create Teleport
if config.teleport.enabled then
local tmpTeleport = Game.createItem(1387, 1, config.teleport.position)
if tmpTeleport then
tmpTeleport:setAttribute(ITEM_ATTRIBUTE_ACTIONID, config.teleport.actionId)
end
end
self.startingUp = true
end
function PaintballEvent.isInRange(self, position)
return isInRange(position, config.spawnArea.fromPosition, config.spawnArea.toPosition)
end
function PaintballEvent.onPlayerCommands(self, player, keyword)
if keyword == 'join' then
if not self.startingUp then
player:sendCancelMessage("The event is not running at the moment.")
return false
end
if self.running then
player:sendCancelMessage("The event has already started.")
return false
end
if player:getGroup():getAccess() then
player:sendCancelMessage("Staff members are not allowed to join the event.")
return false
end
if player:isPzLocked() then
player:sendCancelMessage("You cannot join the event while you are in a fight.")
return false
end
if player:getStorageValue(self.eventStorage) ~= -1 then
player:sendCancelMessage("You have already joined the event.")
return false
end
self:addPlayer(player)
elseif keyword == 'info' then
if not self.running or not self:isInRange(player:getPosition()) then
player:sendCancelMessage("This command is only available for players whom are inside the event.")
return false
end
local text = string.format("You have %d points.\nYou have %d ammo left.\n------------------\nThe current scores in the event is:\n", math.max(0, player:getStorageValue(self.pointsStorage)), player:getStorageValue(self.ammoStorage))
local topScorers = self:getTopScorers()
if #topScorers ~= 0 then
text = string.format("%s\nTop Scorers:\n", text)
for scoreId, scorer in ipairs(topScorers) do
self:addReward(scorer.id, scoreId)
text = string.format("%s%d%s. %s - %d points.\n", text, scoreId, getNumberOrdinal(scoreId), scorer.name, scorer.points)
end
end
player:popupFYI(text)
elseif keyword == 'ammo' then
if not self.running or not self:isInRange(player:getPosition()) then
player:sendCancelMessage("This command is only available for players whom are inside the event.")
return false
end
if config.ammunition.infinite then
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "The ammunition is infinite, there is no need to buy more.")
return false
end
local pointsStorage = math.max(0, player:getStorageValue(self.pointsStorage))
if pointsStorage == 0 then
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You do not have enough points to buy ammunition, you need " .. 1 - pointsStorage .. " more.")
return false
end
player:setStorageValue(self.pointsStorage, pointsStorage - 1)
player:setStorageValue(self.ammoStorage, player:getStorageValue(self.ammoStorage) + config.ammunition.eachPoint)
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You have received " .. config.ammunition.eachPoint .. " bullets and you have lost 1 score point.")
elseif keyword == 'shoot' then
if not self.running or not self:isInRange(player:getPosition()) then
player:sendCancelMessage("This command is only available for players whom are inside the event.")
return false
end
local currentTime = os.time()
if player:getStorageValue(self.exhaustStorage) > currentTime then
player:sendCancelMessage("Gun is on cooldown")
return false
end
local ammoStorage = math.max(0, player:getStorageValue(self.ammoStorage))
if ammoStorage == 0 then
player:sendCancelMessage("You are out of ammunition, you can exchange points for ammunition by typing !paintball ammo, or die for a recharge.")
return false
end
if not config.ammunition.infinite then
player:setStorageValue(self.ammoStorage, ammoStorage - 1)
end
player:setStorageValue(self.exhaustStorage, currentTime + config.bullets.exhaustion)
self:onPlayerShoot(player:getId(), player:getDirection(), player:getPosition(), 1)
else
player:sendCancelMessage("Insufficient parameters.")
end
end
function PaintballEvent.onPlayerDeath(self, playerId, killerId)
if not self.running then
return
end
local player = Player(playerId)
if not player then
return
end
local killer = Player(killerId)
if not killer then
return
end
local playerName, killerName = player:getName(), killer:getName()
-- Killer Actions
killer:setStorageValue(self.pointsStorage, math.max(0, killer:getStorageValue(self.pointsStorage)) + config.points.onKill)
killer:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You killed " .. playerName .. ".")
-- Player Actions
if config.points.removeOnDeath then
player:setStorageValue(self.pointsStorage, player:getStorageValue(self.pointsStorage) - config.points.removePoints)
end
if config.ammunition.refillOnDeath then
player:setStorageValue(self.ammoStorage, config.ammunition.onStart)
end
player:sendTextMessage(MESSAGE_EVENT_ADVANCE, "You were killed by " .. killerName .. ".")
local teleportPosition = getRandomSpawnPosition(config.spawnArea.fromPosition, config.spawnArea.toPosition)
player:teleportTo(teleportPosition)
teleportPosition:sendMagicEffect(CONST_ME_TELEPORT)
-- Broadcast "Death"
self:broadcastMessage(MESSAGE_EVENT_ADVANCE, string.format("[Paintball Event]: %s were killed by %s.", playerName, killerName))
end
function PaintballEvent.onPlayerShoot(self, playerId, direction, fromPosition, distance)
if not self.running then
return
end
local targetPosition = fromPosition + Position.directionOffset[direction]
if blockProjectile(targetPosition) then
targetPosition:sendMagicEffect(CONST_ME_LOSEENERGY)
return
end
fromPosition:sendDistanceEffect(targetPosition, CONST_ANI_SNOWBALL)
local tile = Tile(targetPosition)
if tile then
local creature = tile:getTopCreature()
if creature and creature:isPlayer() then
local creatureId = creature:getId()
if creatureId ~= playerId then
self:onPlayerDeath(creatureId, playerId)
targetPosition:sendMagicEffect(CONST_ME_DRAWBLOOD)
return
end
end
end
distance = distance + 1
if distance ~= config.bullets.maxDistance then
addEvent(function(self, playerId, direction, fromPosition, distance) self:onPlayerShoot(playerId, direction, fromPosition, distance) end, config.bullets.speed, self, playerId, direction, targetPosition, distance)
end
end
function PaintballEvent.releasePlayer(self, playerId, message)
local player = Player(playerId)
if not player then
return
end
-- Send Text Message
if message then
player:sendTextMessage(MESSAGE_INFO_DESCR, message)
end
-- Add Health/Mana
player:addHealth(player:getMaxHealth())
player:addMana(player:getMaxMana())
-- Remove Storages
player:setStorageValue(self.eventStorage, -1)
player:setStorageValue(self.ammoStorage, -1)
player:setStorageValue(self.pointsStorage, -1)
player:setStorageValue(self.exhaustStorage, -1)
-- Teleport player to temple
local templePosition = player:getTown():getTemplePosition()
player:teleportTo(templePosition)
templePosition:sendMagicEffect(CONST_ME_TELEPORT, player)
end
function PaintballEvent.removePlayer(self, playerId, message)
if not self.running or #self.players == 0 then
return
end
for i = 1, #self.players do
if self.players[i] == playerId then
self:releasePlayer(playerId, message)
table.remove(self.players, i)
return
end
end
end
function PaintballEvent.start(self)
if not self.startingUp or self.running then
return
end
if #self.players < self.minPlayers then
self:stop("The Paintball Event did not start due to not enough participants. You got sent back to your hometown.")
Game.broadcastMessage("The Paintball Event did not start due to not enough participants.", MESSAGE_EVENT_ADVANCE)
return
end
for i = 1, #self.players do
local tmpPlayer = Player(self.players[i])
if tmpPlayer then
tmpPlayer:teleportTo(getRandomSpawnPosition(config.spawnArea.fromPosition, config.spawnArea.toPosition))
end
end
self:broadcastMessage(MESSAGE_EVENT_ADVANCE, "Welcome to paintball, the following commands are available:\n!paintball shoot --This will shot a bullet.\n!paintball ammo --This will give you " .. config.ammunition.eachPoint .. " bullets and take 1 point from your current score (you need at least 1 point to use this command).\n!paintball info --This will show you your current score and ammo, it'll also show the current high score of the event.\nIt is strongly recommended that you bind these commands to your hotkeys.")
self.running = true
self.startingUp = false
self.endEvent = addEvent(function(self)
local text = "The Paintball Event has ended."
local topScorers = self:getTopScorers()
if #topScorers ~= 0 then
text = string.format("%s\nTop Scorers:\n", text)
for scoreId, scorer in ipairs(topScorers) do
self:addReward(scorer.id, scoreId)
text = string.format("%s%d%s. %s - %d points.\n", text, scoreId, getNumberOrdinal(scoreId), scorer.name, scorer.points)
end
end
self:stop(text)
end, config.minutesToEnd * 60000, self)
Game.broadcastMessage("The Paintball Event has started. Good luck to all participants!", MESSAGE_EVENT_ADVANCE)
end
function PaintballEvent.stop(self, message)
if #self.players ~= 0 then
for i = 1, #self.players do
self:releasePlayer(self.players[i], message)
end
end
if self.endEvent then
stopEvent(self.endEvent)
end
-- Remove Teleport
if config.teleport.enabled then
local tmpTeleport = Tile(config.teleport.position):getItemById(1387)
if tmpTeleport then
tmpTeleport:remove()
end
end
self.players = { }
self.running = false
self.startingUp = false
self.endEvent = nil
end
local function pushPlayer(player, text, position, fromPosition)
if not player then
return
end
player:sendCancelMessage(text)
player:teleportTo(fromPosition)
position:sendMagicEffect(CONST_ME_TELEPORT)
fromPosition:sendMagicEffect(CONST_ME_TELEPORT)
end
function onStepIn(creature, item, position, fromPosition)
local player = creature:getPlayer()
if not player then
return true
end
if not PaintballEvent.startingUp then
pushPlayer(player, "The event is not running at the moment.", position, fromPosition)
return true
end
if PaintballEvent.running then
pushPlayer(player, "The event has already started.", position, fromPosition)
return true
end
if player:getGroup():getAccess() then
pushPlayer(player, "Staff members are not allowed to join the event.", position, fromPosition)
return true
end
if player:isPzLocked() then
pushPlayer(player, "You cannot join the event while you are in a fight.", position, fromPosition)
return true
end
if player:getStorageValue(PaintballEvent.eventStorage) ~= -1 then
pushPlayer(player, "You have already joined the event.", position, fromPosition)
return true
end
PaintballEvent:addPlayer(player)
return true
end
function onSay(player, words, param)
if param == "" then
player:sendCancelMessage("Insufficient parameters.")
return false
end
PaintballEvent:onPlayerCommands(player, param:lower())
return false
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment