Nyarl666
Member
- Joined
- Sep 25, 2022
- Messages
- 63
- Reaction score
- 7
Super, but Guards always they turn down when they are back in position, not in the direction I put them in on the map editor.Here is a slight variant of this
It's cleaner.
The NPC stays in place and doesn't move, like a palace guard.
It returns to its position/direction after its finished attacking.
Guard South.xml
XML:<?xml version="1.0" encoding="UTF-8"?> <npc name="Guard" script="guard_south.lua" walkinterval="0" floorchange="0" speed="200"> <health now="100" max="100" /> <look type="268" head="76" body="38" legs="76" feet="95" addons="2" /> </npc>
guard_south.lua
Lua:local keywordHandler = KeywordHandler:new() local npcHandler = NpcHandler:new(keywordHandler) NpcSystem.parseParameters(npcHandler) local config = { attackRadius = {x = 7, y = 5, targetDistance = 3, walkDistance = 7}, attackPK = {value = true, skulls = {SKULL_WHITE, SKULL_RED}}, attackMonster = {value = true, ignore = {"Rat", "Cave Rat"}, attackSummons = false}, meleeDamageValue = {min = 100, max = 250}, rangedDamageValue = {min = 50, max = 100} } -- Cache local pvpNpcs = {} -- Can target be attacked local function isAttackable(self) if not self:isNpc() then -- Not NPC if self:isPlayer() and not self:getGroup():getAccess() then -- Player if config.attackPK.value and isInArray(config.attackPK.skulls, self:getSkull()) then -- Player has skull local playerTile = Tile(self:getPosition()) if playerTile:hasFlag(TILESTATE_PROTECTIONZONE) == false and playerTile:hasFlag(TILESTATE_HOUSE) == false then -- if player not in PZ return true end end end if self:isMonster() and config.attackMonster.value then local master = self:getMaster() if not master then -- Monster if not isInArray(config.attackMonster.ignore, self:getName()) then return true end else -- Summon if master:isMonster() then -- always attack monster summons return true else if config.attackMonster.attackSummons then -- check for player summons return true end end end end end return false end -- Find target local function searchTarget(npcId) local npc = Npc(npcId) if not npc then return false end local attackRadius = config.attackRadius for _, spectator in ipairs(Game.getSpectators(npc:getPosition(), false, false, attackRadius.x, attackRadius.x, attackRadius.y, attackRadius.y)) do if isAttackable(spectator) then -- if spectator:getPosition():isSightClear(npc:getPosition(), true) if npc:getPathTo(spectator:getPosition()) and spectator:getPosition():getDistance(pvpNpcs[npcId].spawnPosition) <= attackRadius.walkDistance then pvpNpcs[npcId].npcTarget = spectator:getId() end end end end -- We're stuck with 1000ms ticks, so make function that we can delay for better visuals local function rangedAttack(npcId, targetId) local npc = Npc(npcId) local creature = Creature(targetId) if npc and creature then doTargetCombatHealth(npcId, targetId, COMBAT_PHYSICALDAMAGE, -config.rangedDamageValue.min, -config.rangedDamageValue.max, CONST_ME_NONE) npc:getPosition():sendDistanceEffect(creature:getPosition(), CONST_ANI_BOLT) end end -- Register PVP-NPC defaults function onCreatureAppear(self) local npc = Npc() if npc == self then local npc = Npc(self) local npcId = npc:getId() if not pvpNpcs[npcId] then pvpNpcs[npcId] = {} pvpNpcs[npcId].spawnPosition = npc:getPosition() pvpNpcs[npcId].npcTarget = 0 pvpNpcs[npcId].melee = true pvpNpcs[npcId].ranged = true pvpNpcs[npcId].focus = 0 pvpNpcs[npcId].direction = DIRECTION_SOUTH pvpNpcs[npcId].timeout = 0 -- Randomize gender and hair colour local outfit = Creature(npc:getId()):getOutfit() local lookTypes = { 268, 269 } local hairTypes = { 2, 58, 12, 97, 38, 37, 29, 22, 95, 115, 96, 39 } outfit.lookType = lookTypes[math.random(1,#lookTypes)] outfit.lookHead = hairTypes[math.random(1,#hairTypes)] Creature(npc:getId()):setOutfit(outfit) end end npcHandler:onCreatureAppear(self) end function onCreatureDisappear(cid) npcHandler:onCreatureDisappear(cid) end function onCreatureSay(cid, type, msg) npcHandler:onCreatureSay(cid, type, msg) end function onThink() -- Defaults local npc = Npc() local npcId = npc:getId() local npcPosition = npc:getPosition() local targetId = 0 -- Populate npcTarget if it exists if pvpNpcs[npcId] then targetId = pvpNpcs[npcId].npcTarget end -- Check target local target = Creature(targetId) if not target then -- Not target if pvpNpcs[npcId].npcTarget ~= 0 then -- Target just died or logged out pvpNpcs[npcId].npcTarget = 0 doNpcSetCreatureFocus(0) searchTarget(npcId) elseif npc:getPosition() == pvpNpcs[npcId].spawnPosition then if npc:getDirection() ~= pvpNpcs[npcId].direction then npc:setDirection(pvpNpcs[npcId].direction) end searchTarget(npcId) else local offsetX = npcPosition.x - pvpNpcs[npcId].spawnPosition.x local offsetY = npcPosition.y - pvpNpcs[npcId].spawnPosition.y if math.abs(offsetX) <= 1 and math.abs(offsetY) <= 1 then Creature(npcId):teleportTo(pvpNpcs[npcId].spawnPosition, true) else if pvpNpcs[npcId].timeout < 10 then selfMoveTo(pvpNpcs[npcId].spawnPosition.x, pvpNpcs[npcId].spawnPosition.y, pvpNpcs[npcId].spawnPosition.z) pvpNpcs[npcId].timeout = pvpNpcs[npcId].timeout + 1 else Creature(npcId):teleportTo(pvpNpcs[npcId].spawnPosition) pvpNpcs[npcId].spawnPosition:sendMagicEffect(CONST_ME_TELEPORT) pvpNpcs[npcId].timeout = 0 end end end else -- Reset timeout pvpNpcs[npcId].timeout = 0 -- Check target is still in range local targetPosition = target:getPosition() local targetTile = Tile(targetPosition) if targetTile:hasFlag(TILESTATE_PROTECTIONZONE) or targetTile:hasFlag(TILESTATE_HOUSE) then -- if player in PZ pvpNpcs[npcId].npcTarget = 0 doNpcSetCreatureFocus(0) searchTarget(npcId) npcHandler:onThink() return end local offsetX = npcPosition.x - targetPosition.x local offsetY = npcPosition.y - targetPosition.y local radius = config.attackRadius if math.abs(offsetX) <= radius.x and math.abs(offsetY) <= radius.y and npcPosition.z == targetPosition.z then -- If target still onscreen if targetPosition:getDistance(pvpNpcs[npcId].spawnPosition) <= radius.walkDistance then -- If NPC walked too far from spawnPosition doNpcSetCreatureFocus(targetId) -- Targeted voices if pvpNpcs[npcId].focus ~= targetId then pvpNpcs[npcId].focus = targetId if target:isPlayer() then local playerVoice = { {"STOP RIGHT THERE! CRIMINAL SCUM", TALKTYPE_YELL}, {"Today you shall die... " .. target:getName() .. ".", TALKTYPE_SAY}, {"I WILL NOT TOLERATE VIOLENCE HERE!", TALKTYPE_YELL}, {"Time to die, fool!", TALKTYPE_SAY}, {"It's time to meet my blade - thug!", TALKTYPE_SAY} } local line = math.random(#playerVoice) npc:say(playerVoice[line][1], playerVoice[line][2]) elseif target:isMonster() then local monsterVoice = { {"There is a monster inside the town walls!", TALKTYPE_SAY}, {"Now how did you get in here?!", TALKTYPE_SAY}, {"RUN CITIZENS! A " .. target:getName():upper() .. " HAS BREACHED THE CITY!", TALKTYPE_YELL}, {"Quick! slay the " .. target:getName():lower() .. "!", TALKTYPE_SAY}, {"I will end you!", TALKTYPE_SAY}, {"CHARGE!", TALKTYPE_YELL}, {"COME KILL THIS " .. target:getName():upper() .. "!", TALKTYPE_YELL} } local line = math.random(#monsterVoice) npc:say(monsterVoice[line][1], monsterVoice[line][2]) end end -- Battle voices if pvpNpcs[npcId].melee then -- reuse code that makes 2000ms ticks if math.random(1,10) <= 1 then local battleVoice = { {"I'll make this quick!", TALKTYPE_MONSTER_SAY}, {"GUARDS! TO ME!", TALKTYPE_MONSTER_YELL}, {"You belong in the ground!", TALKTYPE_MONSTER_SAY}, {"Fool!", TALKTYPE_MONSTER_SAY}, {"No mercy!", TALKTYPE_MONSTER_SAY}, {"You're going to regret that!", TALKTYPE_MONSTER_SAY} } local line = math.random(#battleVoice) npc:say(battleVoice[line][1], battleVoice[line][2]) end end -- Follow Target if npcPosition:getDistance(targetPosition) > 1 then selfMoveTo(targetPosition.x, targetPosition.y, targetPosition.z) end -- Ranged Attack if npcPosition:getDistance(targetPosition) <= radius.targetDistance then if pvpNpcs[npcId].ranged == true then if math.random(1,10) > 6 then -- 33% chance to shoot a bolt -- Ranged Hit addEvent(rangedAttack, math.random(1000,1500), npcId, targetId) -- delayed for better visuals end pvpNpcs[npcId].ranged = false -- exhaust for one tick else pvpNpcs[npcId].ranged = true -- toggle exhaust off now end end -- Melee Attack if npcPosition:getDistance(targetPosition) <= 1 then if pvpNpcs[npcId].melee == true then if math.random(1,10) > 2 then -- 80% chance to hit -- Melee Hit doTargetCombatHealth(npcId, targetId, COMBAT_PHYSICALDAMAGE, -config.meleeDamageValue.min, -config.meleeDamageValue.max, CONST_ME_NONE) -- doTargetCombatHealth(npcId, targetId, COMBAT_PHYSICALDAMAGE, -math.floor(10 / 100 * target:getMaxHealth()), -math.floor(25 / 100 * target:getMaxHealth()), CONST_ME_NONE) else -- missed targetPosition:sendMagicEffect(CONST_ME_BLOCKHIT) end pvpNpcs[npcId].melee = false -- exhaust for one tick else pvpNpcs[npcId].melee = true -- toggle exhaust off now end end else selfMoveTo(pvpNpcs[npcId].spawnPosition.x, pvpNpcs[npcId].spawnPosition.y, pvpNpcs[npcId].spawnPosition.z) pvpNpcs[npcId].npcTarget = 0 doNpcSetCreatureFocus(0) end else pvpNpcs[npcId].npcTarget = 0 doNpcSetCreatureFocus(0) searchTarget(npcId) end end npcHandler:onThink() end npcHandler:setMessage(MESSAGE_WALKAWAY, 'That was rude.') npcHandler:setMessage(MESSAGE_FAREWELL, 'Be safe, |PLAYERNAME|.') npcHandler:setMessage(MESSAGE_GREET, 'Greetings |PLAYERNAME|, I am a keeper of the city.') npcHandler:addModule(FocusModule:new())
How to fix the onLook description:
events\scripts\player.lua
replace:
with:Lua:function Player:onLook(thing, position, distance) local description = "You see " .. thing:getDescription(distance)
Lua:function Player:onLook(thing, position, distance) local description = "You see " .. thing:getDescription(distance) if Npc(thing) then if thing:getName() == "Guard" then description = "You see a guard." end end
/reload npcs resets their home position to their current position.
You don't want to do this in production if a guard is off his square chasing something.
Edits:
- Added voices
- Added lookType and lookHead randomizer
- Added a 10 second time-out reset teleport so players can't mess with it.
- percent damage example:
-- doTargetCombatHealth(npcId, targetId, COMBAT_PHYSICALDAMAGE, -math.floor(10 / 100 * target:getMaxHealth()), -math.floor(25 / 100 * target:getMaxHealth()), CONST_ME_NONE)
- Added onLook changes
Where I can change this? Bicouse this is SOUTH guard i need WEST, EAST and NORTH to all gates
edit:
I find! Sorry
pvpNpcs[npcId].direction = DIRECTION_SOUTH
Last edited: