Stealth Detection System (SDS)
Multi-style stealth system (v1.1 since June 17 2008)
Multi-style stealth system (v1.1 since June 17 2008)
Version: 1.1
Creator: Cmpsr2000
VX Release Date: June 17 2008
Exclusive VX Version: Stealth Detection System for VX
Edited by: Night_Runner
XP Release Date: July 13 2010
Introduction
SDS allows the developer to build games that include "Stealth" features similar to certain Final Fantasy games or even Metal Gear Solid.
.
Features
Make any event "aware" of its surroundings, effectively turning it into a "guard"
Execute 4 different actions on detection:
Mode 0: Send to Jail (transfer player)
Mode 1: Game Over
Mode 2: Common Event
Mode 3: MGS Mode
Create a "jail" event anywhere on the map!
MGS Mode allows guards to be alerted before trying to capture the player
Guard intercept speed in MGS mode can be customized
Final capture action in MGS mode can be customized
Guard search time in MGS mode can be customized
Guards can have custom sight ranges
Guards can have tunnel vision or a "cone" field of view
Tunnel width for tunnel vision can be customized
Guards' sight is blocked by objects by default, but this can be disabled by granting "truesight"
Mode 2 can execute any custom event you specify
Guards have a circle of awareness around themselves that can be disabled if desired
Script
Redefinitions
CODE
#-------------------------------------------------------------------------------
# This is a surprise ^^
#-------------------------------------------------------------------------------
module Sound
def self.play_found
Audio.se_play("Audio/SE/found.wav", 100, 100)
end
end
#-------------------------------------------------------------------------------
# This contains command for object movement and can be reused and included in
# any game_character or it's descendants.
#-------------------------------------------------------------------------------
module Movement
#-----------------------------------------------------------------------------
# Checks to see whether this object has moved since the last check.
# RETURNS: Boolean
#-----------------------------------------------------------------------------
def moved?
if self.x != @oldX or self.y != @oldY
setPosition
return true
else
return false
end
end
#-----------------------------------------------------------------------------
# Checks to see whether this object has turned.
# RETURNS: Boolean
#-----------------------------------------------------------------------------
def turned?
if self.direction != @oldDirection
setDirection
return true
else
return false
end
end
#-----------------------------------------------------------------------------
# Saves the current direction so that it can be checked against after a turn
#-----------------------------------------------------------------------------
def setDirection
@oldDirection = self.direction
end
#-----------------------------------------------------------------------------
# Saves the current position so that it can be checked against after a move
#-----------------------------------------------------------------------------
def setPosition
@oldX = self.x
@oldY = self.y
end
#-----------------------------------------------------------------------------
# Changes the object's route to perform an in-place spin while waiting after
# each turn. It is recommended to call object.lock before using this, and
# call object.unlock when it has complete.
#-----------------------------------------------------------------------------
def spinInPlace(waitTime)
spinRoute = RPG::MoveRoute.new
spinRoute.repeat = false
#spinRoute.wait = true
for x in 0..3
spinRoute.list.push(RPG::MoveCommand.new(15, [waitTime]))
spinRoute.list.push(RPG::MoveCommand.new(16 + x))
end
spinRoute.list.push(RPG::MoveCommand.new(15, [waitTime]))
spinRoute.list.push(RPG::MoveCommand.new(0))
spinRoute.list.delete_at(0) #weird RGSS Anomoly, must delete empty position
self.move_route = spinRoute
self.move_route_index = 0
end
#-----------------------------------------------------------------------------
# This is an advanced pathfinding algorithm that returns a boolean indicating
# whether or not it was able to find a path from the start point (the object's
# current location) to the endpoint, then changes the object's move values
# to initiate movement. RETURNS: Boolean
# finalX: x coordinate of the destination
# finalY: y coordinate of the destination
#-----------------------------------------------------------------------------
def goto(finalX, finalY)
return true if self.x == finalX and self.y == finalY
startX = self.x
startY = self.y
#figure out the route: A* algorithm
@activeList = []
@activeParents = []
@activeScores = []
@closedList = []
@closedParents = []
@closedScores = []
@activeList.push([startX, startY])
@activeParents.push([startX, startY])
@activeScores.push([0,0,0])
noPath = false
finalNode = [finalX, finalY]
#main logic
while @closedList.index(finalNode) == nil
nodeToCheck = @activeList[0] #lowest F is always in position 0!
if nodeToCheck == nil
noPath = true
break
end
moveToClosed(nodeToCheck)
for node in adjacentNodes(nodeToCheck) #a "node" is an array: [x, y] !!!
if ($game_map.passable?(node[0], node[1], 0) and
@closedList.index(node) == nil) or node == finalNode
index = @activeList.index(node)
if index == nil
scores = calcScores(node, nodeToCheck, finalNode)
insertionPoint = findInsertionPoint(scores[0])
@activeList.insert(insertionPoint, node)
@activeParents.insert(insertionPoint, nodeToCheck)
@activeScores.insert(insertionPoint, scores)
else
g = calcG(nodeToCheck)
if g < @activeScores[index][1]
@activeParents[index] = nodeToCheck
h = @activeScores[index][2]
f = g + h
@activeScores[index] = [f, g, h]
insertionPoint = findInsertionPoint(@activeScores[index][0])
#we don't need to reinsert if we already exist in the right place
if insertionPoint != index
tempNode = @activeList[index]
tempParent = @activeParents[index]
tempScores = @activeScores[index]
#delete existing
@activeList.delete(tempNode)
@activeParents.delete(tempParent)
@activeScores.delete(tempScores)
#reinsert
@activeList.insert(insertionPoint, tempNode)
@activeParents.insert(insertionPoint, tempParent)
@activeScores.insert(insertionPoint, tempScores)
end
end
end
end
end
end
#return if there is no path, otherwise build the path
return false if noPath
path = [finalNode]
parent = @closedParents[@closedList.index(finalNode)]
while parent != [startX, startY]
path.push(parent)
parent = @closedParents[@closedList.index(parent)]
end
path.reverse! #put the path in order of execution
#make a new route object that doesn't repeat (one time use only!)
gotoRoute = RPG::MoveRoute.new
gotoRoute.repeat = false
#itterate through the path and add the appropriate commands to the route
#we can't move diagonal, so only one x or one y should change between steps
lastNode = [startX, startY]
for node in path
if node[0] > lastNode[0]
commandCode = 3 # move right
elsif node[0] < lastNode[0]
commandCode = 2 # move left
else
if node[1] > lastNode[1]
commandCode = 1 # move down
elsif node[1] < lastNode[1]
commandCode = 4 # move up
end
end
lastNode = node
gotoRoute.list.push(RPG::MoveCommand.new(commandCode))
end
gotoRoute.list.push(RPG::MoveCommand.new(0))
gotoRoute.list.delete_at(0) #weird RGSS Anomoly, must delete empty position
#set the object's route and return!
self.move_route = gotoRoute
self.move_route_index = 0
return true
end
#-----------------------------------------------------------------------------
# moves the active node to the closed list so it's not checked twice!
#-----------------------------------------------------------------------------
def moveToClosed(activeNode)
index = @activeList.index(activeNode)
#add to closed
@closedList.push(@activeList[index])
@closedParents.push(@activeParents[index])
@closedScores.push(@activeScores[index])
#delete from active
@activeList.delete_at(index)
@activeParents.delete_at(index)
@activeScores.delete_at(index)
end
#-----------------------------------------------------------------------------
# finds the array insertion point based on the f score
#-----------------------------------------------------------------------------
def findInsertionPoint(f)
insertionPoint = 0
if @activeList.length != 0
while insertionPoint < @activeList.length
if f <= @activeScores[insertionPoint][0]
break
else
insertionPoint += 1
end
end
end
return insertionPoint
end
#-----------------------------------------------------------------------------
# calculates all the scores for a node
#-----------------------------------------------------------------------------
def calcScores(node, parentNode, finalNode)
h = calcH(node, finalNode)
g = calcG(parentNode)
f = g + h
return [f, g, h]
end
#-----------------------------------------------------------------------------
# calculates the G value (route distance)
#-----------------------------------------------------------------------------
def calcG(parentNode)
#can't move diagonly: just add 1 to parent score
index = @closedList.index(parentNode)
score = @closedScores[index][1] + 1
return score
end
#-----------------------------------------------------------------------------
# claculates the H value (distance from start to finish)
#-----------------------------------------------------------------------------
def calcH(node, finalNode)
#Manhattan method is admissible in this context
score = (node[0] - finalNode[0]).abs + (node[1] - finalNode[1]).abs
return score
end
#-----------------------------------------------------------------------------
# Finds the adjacent nodes of the active node
#-----------------------------------------------------------------------------
def adjacentNodes(sourceNode)
node1 = [sourceNode[0] + 1, sourceNode[1]]
node2 = [sourceNode[0], sourceNode[1] + 1]
node3 = [sourceNode[0] - 1, sourceNode[1]]
node4 = [sourceNode[0], sourceNode[1] - 1]
return [node1, node2, node3, node4]
end
end
#-------------------------------------------------------------------------------
# the instance of $game_stealth needs to be created
#-------------------------------------------------------------------------------
class Scene_Title
alias oldCommandNewGameStealth command_new_game
def command_new_game
$game_stealth = Game_Stealth.new
oldCommandNewGameStealth
end
end
#-------------------------------------------------------------------------------
# saveing and loading $game_stealth
#-------------------------------------------------------------------------------
class Scene_Save
#--------------------------------------------------------------------------
# * Write Save Data
# file : write file object (opened)
#--------------------------------------------------------------------------
alias old_WriteSaveDataStealth write_save_data
def write_save_data(file)
old_WriteSaveDataStealth(file)
Marshal.dump($game_stealth, file)
end
end
class Scene_Load
#--------------------------------------------------------------------------
# * Read Save Data
# file : write file object (opened)
#--------------------------------------------------------------------------
alias old_ReadSaveDataStealth read_save_data
def read_save_data(file)
old_ReadSaveDataStealth(file)
$game_stealth = Marshal.load(file)
end
end
#-------------------------------------------------------------------------------
# The stealth system needs to be updated every frame along with the map scene
#-------------------------------------------------------------------------------
class Scene_Map
alias oldUpdateStealth update
def update
oldUpdateStealth
unless $game_temp.message_window_showing # Unless displaying a message
$game_stealth.update
end
end
end
class Game_Character
alias nr_sds_initialize initialize
def initialize(*args)
nr_sds_initialize(*args)
@move_failed = false
end
#--------------------------------------------------------------------------
# * Determine if Stopping
#--------------------------------------------------------------------------
def stopping?
return (not (moving? or jumping?))
end
#--------------------------------------------------------------------------
# * Move Down
# turn_enabled : a flag permits direction change on that spot
#--------------------------------------------------------------------------
def move_down(turn_enabled = true)
# Turn down
if turn_enabled
turn_down
end
# If passable
if passable?(@x, @y, 2)
# Turn down
turn_down
# Update coordinates
@y += 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
# If impassable
else
# Determine if touch event is triggered
check_event_trigger_touch(@x, @y+1)
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Left
# turn_enabled : a flag permits direction change on that spot
#--------------------------------------------------------------------------
def move_left(turn_enabled = true)
# Turn left
if turn_enabled
turn_left
end
# If passable
if passable?(@x, @y, 4)
# Turn left
turn_left
# Update coordinates
@x -= 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
# If impassable
else
# Determine if touch event is triggered
check_event_trigger_touch(@x-1, @y)
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Right
# turn_enabled : a flag permits direction change on that spot
#--------------------------------------------------------------------------
def move_right(turn_enabled = true)
# Turn right
if turn_enabled
turn_right
end
# If passable
if passable?(@x, @y, 6)
# Turn right
turn_right
# Update coordinates
@x += 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
# If impassable
else
# Determine if touch event is triggered
check_event_trigger_touch(@x+1, @y)
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move up
# turn_enabled : a flag permits direction change on that spot
#--------------------------------------------------------------------------
def move_up(turn_enabled = true)
# Turn up
if turn_enabled
turn_up
end
# If passable
if passable?(@x, @y, 8)
# Turn up
turn_up
# Update coordinates
@y -= 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
# If impassable
else
# Determine if touch event is triggered
check_event_trigger_touch(@x, @y-1)
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Lower Left
#--------------------------------------------------------------------------
def move_lower_left
# If no direction fix
unless @direction_fix
# Face down is facing right or up
@direction = (@direction == 6 ? 4 : @direction == 8 ? 2 : @direction)
end
# When a down to left or a left to down course is passable
if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 4)) or
(passable?(@x, @y, 4) and passable?(@x - 1, @y, 2))
# Update coordinates
@x -= 1
@y += 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
else
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Lower Right
#--------------------------------------------------------------------------
def move_lower_right
# If no direction fix
unless @direction_fix
# Face right if facing left, and face down if facing up
@direction = (@direction == 4 ? 6 : @direction == 8 ? 2 : @direction)
end
# When a down to right or a right to down course is passable
if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 6)) or
(passable?(@x, @y, 6) and passable?(@x + 1, @y, 2))
# Update coordinates
@x += 1
@y += 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
else
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Upper Left
#--------------------------------------------------------------------------
def move_upper_left
# If no direction fix
unless @direction_fix
# Face left if facing right, and face up if facing down
@direction = (@direction == 6 ? 4 : @direction == 2 ? 8 : @direction)
end
# When an up to left or a left to up course is passable
if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 4)) or
(passable?(@x, @y, 4) and passable?(@x - 1, @y, 8))
# Update coordinates
@x -= 1
@y -= 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
else
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Upper Right
#--------------------------------------------------------------------------
def move_upper_right
# If no direction fix
unless @direction_fix
# Face right if facing left, and face up if facing down
@direction = (@direction == 4 ? 6 : @direction == 2 ? 8 : @direction)
end
# When an up to right or a right to up course is passable
if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 6)) or
(passable?(@x, @y, 6) and passable?(@x + 1, @y, 8))
# Update coordinates
@x += 1
@y -= 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
else
# Set move failed flag
@move_failed = true
end
end
end
class Game_Player < Game_Character
include Movement
attr_accessor :detected
def transfer(new_map, new_x, new_y, new_dir)
# Set transferring player flag
$game_temp.player_transferring = true
# Set player move destination
$game_temp.player_new_map_id = new_map
$game_temp.player_new_x = new_x
$game_temp.player_new_y = new_y
$game_temp.player_new_direction = new_dir
Graphics.freeze
# Set transition processing flag
$game_temp.transition_processing = true
$game_temp.transition_name = ""
end
end
#-------------------------------------------------------------------------------
# Events have tons of new attributes with the stealth system. Plus, we need to
# include the movement module and initialize awareness to false.
#-------------------------------------------------------------------------------
class Game_Event < Game_Character
include Movement
#for all stealth
attr_accessor :isAware
attr_accessor :detectRange
attr_accessor :tunnelVision
attr_accessor :trueSight
attr_accessor :detectAction
attr_accessor :originalAction
#For MGS-style stealth
attr_accessor :move_speed
attr_accessor :move_frequency
attr_accessor :detectState
attr_accessor :move_route_index
attr_accessor :move_route
attr_accessor :returnX
attr_accessor :returnY
attr_accessor :returnRoute
attr_accessor :returnRouteIndex
attr_accessor :returnSpeed
attr_accessor :returnFrequency
attr_reader :move_failed
alias oldInitStealth initialize
def initialize(map_id, event)
oldInitStealth(map_id, event)
@isAware = false
end
end
# This is a surprise ^^
#-------------------------------------------------------------------------------
module Sound
def self.play_found
Audio.se_play("Audio/SE/found.wav", 100, 100)
end
end
#-------------------------------------------------------------------------------
# This contains command for object movement and can be reused and included in
# any game_character or it's descendants.
#-------------------------------------------------------------------------------
module Movement
#-----------------------------------------------------------------------------
# Checks to see whether this object has moved since the last check.
# RETURNS: Boolean
#-----------------------------------------------------------------------------
def moved?
if self.x != @oldX or self.y != @oldY
setPosition
return true
else
return false
end
end
#-----------------------------------------------------------------------------
# Checks to see whether this object has turned.
# RETURNS: Boolean
#-----------------------------------------------------------------------------
def turned?
if self.direction != @oldDirection
setDirection
return true
else
return false
end
end
#-----------------------------------------------------------------------------
# Saves the current direction so that it can be checked against after a turn
#-----------------------------------------------------------------------------
def setDirection
@oldDirection = self.direction
end
#-----------------------------------------------------------------------------
# Saves the current position so that it can be checked against after a move
#-----------------------------------------------------------------------------
def setPosition
@oldX = self.x
@oldY = self.y
end
#-----------------------------------------------------------------------------
# Changes the object's route to perform an in-place spin while waiting after
# each turn. It is recommended to call object.lock before using this, and
# call object.unlock when it has complete.
#-----------------------------------------------------------------------------
def spinInPlace(waitTime)
spinRoute = RPG::MoveRoute.new
spinRoute.repeat = false
#spinRoute.wait = true
for x in 0..3
spinRoute.list.push(RPG::MoveCommand.new(15, [waitTime]))
spinRoute.list.push(RPG::MoveCommand.new(16 + x))
end
spinRoute.list.push(RPG::MoveCommand.new(15, [waitTime]))
spinRoute.list.push(RPG::MoveCommand.new(0))
spinRoute.list.delete_at(0) #weird RGSS Anomoly, must delete empty position
self.move_route = spinRoute
self.move_route_index = 0
end
#-----------------------------------------------------------------------------
# This is an advanced pathfinding algorithm that returns a boolean indicating
# whether or not it was able to find a path from the start point (the object's
# current location) to the endpoint, then changes the object's move values
# to initiate movement. RETURNS: Boolean
# finalX: x coordinate of the destination
# finalY: y coordinate of the destination
#-----------------------------------------------------------------------------
def goto(finalX, finalY)
return true if self.x == finalX and self.y == finalY
startX = self.x
startY = self.y
#figure out the route: A* algorithm
@activeList = []
@activeParents = []
@activeScores = []
@closedList = []
@closedParents = []
@closedScores = []
@activeList.push([startX, startY])
@activeParents.push([startX, startY])
@activeScores.push([0,0,0])
noPath = false
finalNode = [finalX, finalY]
#main logic
while @closedList.index(finalNode) == nil
nodeToCheck = @activeList[0] #lowest F is always in position 0!
if nodeToCheck == nil
noPath = true
break
end
moveToClosed(nodeToCheck)
for node in adjacentNodes(nodeToCheck) #a "node" is an array: [x, y] !!!
if ($game_map.passable?(node[0], node[1], 0) and
@closedList.index(node) == nil) or node == finalNode
index = @activeList.index(node)
if index == nil
scores = calcScores(node, nodeToCheck, finalNode)
insertionPoint = findInsertionPoint(scores[0])
@activeList.insert(insertionPoint, node)
@activeParents.insert(insertionPoint, nodeToCheck)
@activeScores.insert(insertionPoint, scores)
else
g = calcG(nodeToCheck)
if g < @activeScores[index][1]
@activeParents[index] = nodeToCheck
h = @activeScores[index][2]
f = g + h
@activeScores[index] = [f, g, h]
insertionPoint = findInsertionPoint(@activeScores[index][0])
#we don't need to reinsert if we already exist in the right place
if insertionPoint != index
tempNode = @activeList[index]
tempParent = @activeParents[index]
tempScores = @activeScores[index]
#delete existing
@activeList.delete(tempNode)
@activeParents.delete(tempParent)
@activeScores.delete(tempScores)
#reinsert
@activeList.insert(insertionPoint, tempNode)
@activeParents.insert(insertionPoint, tempParent)
@activeScores.insert(insertionPoint, tempScores)
end
end
end
end
end
end
#return if there is no path, otherwise build the path
return false if noPath
path = [finalNode]
parent = @closedParents[@closedList.index(finalNode)]
while parent != [startX, startY]
path.push(parent)
parent = @closedParents[@closedList.index(parent)]
end
path.reverse! #put the path in order of execution
#make a new route object that doesn't repeat (one time use only!)
gotoRoute = RPG::MoveRoute.new
gotoRoute.repeat = false
#itterate through the path and add the appropriate commands to the route
#we can't move diagonal, so only one x or one y should change between steps
lastNode = [startX, startY]
for node in path
if node[0] > lastNode[0]
commandCode = 3 # move right
elsif node[0] < lastNode[0]
commandCode = 2 # move left
else
if node[1] > lastNode[1]
commandCode = 1 # move down
elsif node[1] < lastNode[1]
commandCode = 4 # move up
end
end
lastNode = node
gotoRoute.list.push(RPG::MoveCommand.new(commandCode))
end
gotoRoute.list.push(RPG::MoveCommand.new(0))
gotoRoute.list.delete_at(0) #weird RGSS Anomoly, must delete empty position
#set the object's route and return!
self.move_route = gotoRoute
self.move_route_index = 0
return true
end
#-----------------------------------------------------------------------------
# moves the active node to the closed list so it's not checked twice!
#-----------------------------------------------------------------------------
def moveToClosed(activeNode)
index = @activeList.index(activeNode)
#add to closed
@closedList.push(@activeList[index])
@closedParents.push(@activeParents[index])
@closedScores.push(@activeScores[index])
#delete from active
@activeList.delete_at(index)
@activeParents.delete_at(index)
@activeScores.delete_at(index)
end
#-----------------------------------------------------------------------------
# finds the array insertion point based on the f score
#-----------------------------------------------------------------------------
def findInsertionPoint(f)
insertionPoint = 0
if @activeList.length != 0
while insertionPoint < @activeList.length
if f <= @activeScores[insertionPoint][0]
break
else
insertionPoint += 1
end
end
end
return insertionPoint
end
#-----------------------------------------------------------------------------
# calculates all the scores for a node
#-----------------------------------------------------------------------------
def calcScores(node, parentNode, finalNode)
h = calcH(node, finalNode)
g = calcG(parentNode)
f = g + h
return [f, g, h]
end
#-----------------------------------------------------------------------------
# calculates the G value (route distance)
#-----------------------------------------------------------------------------
def calcG(parentNode)
#can't move diagonly: just add 1 to parent score
index = @closedList.index(parentNode)
score = @closedScores[index][1] + 1
return score
end
#-----------------------------------------------------------------------------
# claculates the H value (distance from start to finish)
#-----------------------------------------------------------------------------
def calcH(node, finalNode)
#Manhattan method is admissible in this context
score = (node[0] - finalNode[0]).abs + (node[1] - finalNode[1]).abs
return score
end
#-----------------------------------------------------------------------------
# Finds the adjacent nodes of the active node
#-----------------------------------------------------------------------------
def adjacentNodes(sourceNode)
node1 = [sourceNode[0] + 1, sourceNode[1]]
node2 = [sourceNode[0], sourceNode[1] + 1]
node3 = [sourceNode[0] - 1, sourceNode[1]]
node4 = [sourceNode[0], sourceNode[1] - 1]
return [node1, node2, node3, node4]
end
end
#-------------------------------------------------------------------------------
# the instance of $game_stealth needs to be created
#-------------------------------------------------------------------------------
class Scene_Title
alias oldCommandNewGameStealth command_new_game
def command_new_game
$game_stealth = Game_Stealth.new
oldCommandNewGameStealth
end
end
#-------------------------------------------------------------------------------
# saveing and loading $game_stealth
#-------------------------------------------------------------------------------
class Scene_Save
#--------------------------------------------------------------------------
# * Write Save Data
# file : write file object (opened)
#--------------------------------------------------------------------------
alias old_WriteSaveDataStealth write_save_data
def write_save_data(file)
old_WriteSaveDataStealth(file)
Marshal.dump($game_stealth, file)
end
end
class Scene_Load
#--------------------------------------------------------------------------
# * Read Save Data
# file : write file object (opened)
#--------------------------------------------------------------------------
alias old_ReadSaveDataStealth read_save_data
def read_save_data(file)
old_ReadSaveDataStealth(file)
$game_stealth = Marshal.load(file)
end
end
#-------------------------------------------------------------------------------
# The stealth system needs to be updated every frame along with the map scene
#-------------------------------------------------------------------------------
class Scene_Map
alias oldUpdateStealth update
def update
oldUpdateStealth
unless $game_temp.message_window_showing # Unless displaying a message
$game_stealth.update
end
end
end
class Game_Character
alias nr_sds_initialize initialize
def initialize(*args)
nr_sds_initialize(*args)
@move_failed = false
end
#--------------------------------------------------------------------------
# * Determine if Stopping
#--------------------------------------------------------------------------
def stopping?
return (not (moving? or jumping?))
end
#--------------------------------------------------------------------------
# * Move Down
# turn_enabled : a flag permits direction change on that spot
#--------------------------------------------------------------------------
def move_down(turn_enabled = true)
# Turn down
if turn_enabled
turn_down
end
# If passable
if passable?(@x, @y, 2)
# Turn down
turn_down
# Update coordinates
@y += 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
# If impassable
else
# Determine if touch event is triggered
check_event_trigger_touch(@x, @y+1)
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Left
# turn_enabled : a flag permits direction change on that spot
#--------------------------------------------------------------------------
def move_left(turn_enabled = true)
# Turn left
if turn_enabled
turn_left
end
# If passable
if passable?(@x, @y, 4)
# Turn left
turn_left
# Update coordinates
@x -= 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
# If impassable
else
# Determine if touch event is triggered
check_event_trigger_touch(@x-1, @y)
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Right
# turn_enabled : a flag permits direction change on that spot
#--------------------------------------------------------------------------
def move_right(turn_enabled = true)
# Turn right
if turn_enabled
turn_right
end
# If passable
if passable?(@x, @y, 6)
# Turn right
turn_right
# Update coordinates
@x += 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
# If impassable
else
# Determine if touch event is triggered
check_event_trigger_touch(@x+1, @y)
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move up
# turn_enabled : a flag permits direction change on that spot
#--------------------------------------------------------------------------
def move_up(turn_enabled = true)
# Turn up
if turn_enabled
turn_up
end
# If passable
if passable?(@x, @y, 8)
# Turn up
turn_up
# Update coordinates
@y -= 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
# If impassable
else
# Determine if touch event is triggered
check_event_trigger_touch(@x, @y-1)
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Lower Left
#--------------------------------------------------------------------------
def move_lower_left
# If no direction fix
unless @direction_fix
# Face down is facing right or up
@direction = (@direction == 6 ? 4 : @direction == 8 ? 2 : @direction)
end
# When a down to left or a left to down course is passable
if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 4)) or
(passable?(@x, @y, 4) and passable?(@x - 1, @y, 2))
# Update coordinates
@x -= 1
@y += 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
else
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Lower Right
#--------------------------------------------------------------------------
def move_lower_right
# If no direction fix
unless @direction_fix
# Face right if facing left, and face down if facing up
@direction = (@direction == 4 ? 6 : @direction == 8 ? 2 : @direction)
end
# When a down to right or a right to down course is passable
if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 6)) or
(passable?(@x, @y, 6) and passable?(@x + 1, @y, 2))
# Update coordinates
@x += 1
@y += 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
else
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Upper Left
#--------------------------------------------------------------------------
def move_upper_left
# If no direction fix
unless @direction_fix
# Face left if facing right, and face up if facing down
@direction = (@direction == 6 ? 4 : @direction == 2 ? 8 : @direction)
end
# When an up to left or a left to up course is passable
if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 4)) or
(passable?(@x, @y, 4) and passable?(@x - 1, @y, 8))
# Update coordinates
@x -= 1
@y -= 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
else
# Set move failed flag
@move_failed = true
end
end
#--------------------------------------------------------------------------
# * Move Upper Right
#--------------------------------------------------------------------------
def move_upper_right
# If no direction fix
unless @direction_fix
# Face right if facing left, and face up if facing down
@direction = (@direction == 4 ? 6 : @direction == 2 ? 8 : @direction)
end
# When an up to right or a right to up course is passable
if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 6)) or
(passable?(@x, @y, 6) and passable?(@x + 1, @y, 8))
# Update coordinates
@x += 1
@y -= 1
# Increase steps
increase_steps
# Set move failed flag
@move_failed = false
else
# Set move failed flag
@move_failed = true
end
end
end
class Game_Player < Game_Character
include Movement
attr_accessor :detected
def transfer(new_map, new_x, new_y, new_dir)
# Set transferring player flag
$game_temp.player_transferring = true
# Set player move destination
$game_temp.player_new_map_id = new_map
$game_temp.player_new_x = new_x
$game_temp.player_new_y = new_y
$game_temp.player_new_direction = new_dir
Graphics.freeze
# Set transition processing flag
$game_temp.transition_processing = true
$game_temp.transition_name = ""
end
end
#-------------------------------------------------------------------------------
# Events have tons of new attributes with the stealth system. Plus, we need to
# include the movement module and initialize awareness to false.
#-------------------------------------------------------------------------------
class Game_Event < Game_Character
include Movement
#for all stealth
attr_accessor :isAware
attr_accessor :detectRange
attr_accessor :tunnelVision
attr_accessor :trueSight
attr_accessor :detectAction
attr_accessor :originalAction
#For MGS-style stealth
attr_accessor :move_speed
attr_accessor :move_frequency
attr_accessor :detectState
attr_accessor :move_route_index
attr_accessor :move_route
attr_accessor :returnX
attr_accessor :returnY
attr_accessor :returnRoute
attr_accessor :returnRouteIndex
attr_accessor :returnSpeed
attr_accessor :returnFrequency
attr_reader :move_failed
alias oldInitStealth initialize
def initialize(map_id, event)
oldInitStealth(map_id, event)
@isAware = false
end
end
Game_Stealth
CODE
#-------------------------------------------------------------------------------
#
# Stealth Detection System v 1.0
# code by cmpsr2000 @ rpgrevolution.com/forums
# Released June 11, 2008
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
#
# SDS is an advanced stealth detection system for use in RPG Maker VX games.
# To use the system, you need to build an event that begins with a script call:
#
# $game_stealth.detector(eventID, detectRange, detectAction,
# tunnelVision, trueSight)
#
# eventID: The ID of the event. Use @event_id as it's the easiest way
# detectRange: How far the "guard" is able to see. Higher numbers require
# more CPU time to proccess! Lag increases exponentially if
# you are NOT using tunnel vision.
# detectAction: The action to perform when the guard detects the player
# DEFAULT = 1
# 0: Goto jail event
# 1: Game Over
# 2: Execute common event
# 3: MGS Mode
# tunnelVision: determines whether the guard's field of view cones out or
# goes in a straight line.
# DEFAULT = false
# trueSight: determines whether the guard can see through obstacles
# DEFAULT = false
#
# NOTE: You do NOT have to set parameters that have a default.
#
# MGS MODE EXPLAINATION:
#
# When using detectAction 3 (MGS Mode) guards will be "alerted" if
# they spot the player. The guard will run to the position they spotted
# the player at, then begin to look around for the player again. If the
# guard sees the player a second time, the captureAction is performed.
#
#-------------------------------------------------------------------------------
class Game_Stealth
attr_accessor :common_event_id
def initialize
@checkedTiles = []
@fovPassability = {}
@detectingEvent = nil
@tempInterpreter = nil
@alertedGuards = []
#---------------------------------------------------------------------------
# If you use tunnel vision, set the tunnel width here. It MUST be an ODD
# number or the game will round up to the next odd number! Widths over 3
# are not recomended.
#---------------------------------------------------------------------------
@tunnelWidth = 3
#---------------------------------------------------------------------------
# The common event ID to execute when using the common event detect action
#---------------------------------------------------------------------------
@common_event_id = 1
#---------------------------------------------------------------------------
# The speed and frequency the guards move when they detect the player in
# MGS mode.
# speed: 1: x8 slower,
# 2: x4 slower
# 3: x2 slower
# 4: normal
# 5: x2 faster
# 6: x4 faster
#
# frequency: 1: lowest
# 2: lower
# 3: normal
# 4: higher
# 5: highest
#---------------------------------------------------------------------------
@guardInterceptSpeed = 4
@guardInterceptFrequency = 6
#---------------------------------------------------------------------------
# When in MGS mode, the action to be performed if the guard detects the
# player a second time. This is set by default to game_over since all other
# options require some configuration. Valid values are:
# captureAction: 0: Goto jail event
# 1: Game Over
# 2: Execute common event
#---------------------------------------------------------------------------
@captureAction = 1
#---------------------------------------------------------------------------
# How many frames a guard will wait before executing each turn when
# searching for the player after detecting them in MGS mode.
#---------------------------------------------------------------------------
@waitTime = Graphics.frame_rate / 2 # 40
#---------------------------------------------------------------------------
# Change this to false to disable the detection radius around guards.
# This means the player can safely stand next to a guard as long as he's
# not in the guard's line of sight.
#---------------------------------------------------------------------------
@allowDetectRadius= true</FONT></P> <P><FONT size=6><FONT size=2> @tunnelWidth += 1 if (@tunnelWidth % 2) == 1
end
#-----------------------------------------------------------------------------
# Standard update method; checks for player detection then handles MGS
# state changes for guards then checks for player detection again
#-----------------------------------------------------------------------------
def update
if $game_player.detected and not $game_player.moving?
doDetectAction
end
for guard in @alertedGuards
case guard.detectState
when nil, 0 #alerted
guard.lock
guard.returnX = guard.x
guard.returnY = guard.y
guard.returnRoute = guard.move_route
guard.returnRouteIndex = guard.move_route_index
guard.returnSpeed = guard.move_speed
guard.returnFrequency = guard.move_frequency
guard.move_speed = @guardInterceptSpeed
guard.move_frequency = @guardInterceptFrequency
guard.isAware = false
guard.detectState = guard.goto($game_player.x, $game_player.y) ? 1 : 3
guard.unlock
when 1 #intercepting
if (guard.move_route_index == guard.move_route.list.length - 1 and
guard.stopping?) or guard.move_failed
guard.lock
guard.isAware = true
guard.spinInPlace(@waitTime)
guard.detectState = 2
guard.unlock
end
when 2 #checking
if guard.move_route_index == guard.move_route.list.length - 1 and
guard.stopping?
guard.lock
guard.isAware = false
go = guard.goto(guard.returnX, guard.returnY)
guard.detectState = 3
guard.unlock
end
when 3 #returning
if guard.move_route_index == guard.move_route.list.length - 1 and not
guard.moving?
guard.lock
guard.isAware = true
guard.move_route = guard.returnRoute
guard.move_route_index = guard.returnRouteIndex
guard.move_speed = guard.returnSpeed
guard.move_frequency = guard.returnFrequency
guard.detectState = 0
@captured = false
@alertedGuards.delete(guard)
guard.unlock
end
end
end
observe
end
#-----------------------------------------------------------------------------
# Runs through the aware events(guards) on the map and tells them to check
# for the player.
#-----------------------------------------------------------------------------
def observe
for event in $game_map.events.values #events is a hash!
if event.isAware
if event.moved? or event.turned? or $game_player.moved?
if checkForDetection(event)
$game_player.detected = true
@detectingEvent = event
return
end
end
end
@checkedTiles = []
@fovPassability = {}
end
end
#-----------------------------------------------------------------------------
# Runs through each tile in the event(guard)'s field of view and checks
# it for the player. RETURNS: Boolean
# event: The guard that is currently checking for the player.
#-----------------------------------------------------------------------------
def checkForDetection(event)
return true if playerInDetectionRadius(event)
return false if playerOutOfRange(event)
fieldOfView = event.tunnelVision ? @tunnelWidth : 1
for distance in 1..event.detectRange
for width in 1..fieldOfView
return true if checkTile(event, width, distance)
end
fieldOfView += 2 unless event.tunnelVision
end
return false
end
#-----------------------------------------------------------------------------
# Checks a tile for the player. RETURNS: Boolean
# event: The guard that is currently checking for the player.
# width: The width-index of the tile being checked
# distance: The depth-index of the tile being checked
#-----------------------------------------------------------------------------
def checkTile(event, width, distance)
if event.tunnelVision
if @tunnelWidth > 1
width = width - ((@tunnelWidth - 1)/2)
else
width = 0
end
else
width -= distance
end
x = event.x
y = event.y
direction = event.direction
case direction
when 2
y += distance
x += width
when 4
y += width
x -= distance
when 6
y += width
x += distance
when 8
y -= distance
x += width
end
return false if @checkedTiles.index([x,y]) != nil
return false if tileHidden?(event, x, y)
return true if $game_player.x == x and $game_player.y == y
return false
end
#-----------------------------------------------------------------------------
# Determines the visibility of a tile to a guard. RETURNS: Boolean
# event: The guard that is currently checking for the player.
# x: The x coordinate of the tile to be checked
# y: The y coordinate of the tile to be checked
#-----------------------------------------------------------------------------
def tileHidden?(event, x, y)
return false if event.trueSight
#check the current tile first
@checkedTiles.push([x,y])
originalX = x
originalY = y
if not $game_map.passable?(x, y, 0)
@fovPassability[[x,y]] = false
return true
end
#now check all adjacent tiles one step closer to the detector
direction = event.direction
case direction
when 2 #down
y -= 1
for i in (x-1)..(x+1)
#if we've checked the tile, then it exists in the FoV
if @checkedTiles.index([i,y]) != nil
if not @fovPassability[[i,y]]
@fovPassability[[originalX,originalY]] = false
return true
end
end
end
when 4 #left
x += 1
for i in (y-1)..(y+1)
#if we've checked the tile, then it exists in the FoV
if @checkedTiles.index([x,i]) != nil
if not @fovPassability[[x,i]]
@fovPassability[[originalX,originalY]] = false
return true
end
end
end
when 6 #right
x -= 1
for i in (y-1)..(y+1)
#if we've checked the tile, then it exists in the FoV
if @checkedTiles.index([x,i]) != nil
if not @fovPassability[[x,i]]
@fovPassability[[originalX,originalY]] = false
return true
end
end
end
when 8 #up
y += 1
for i in (x-1)..(x+1)
#if we've checked the tile, then it exists in the FoV
if @checkedTiles.index([i,y]) != nil
if not @fovPassability[[i,y]]
@fovPassability[[originalX,originalY]] = false
return true
end
end
end
end
#this tile and the blocking tiles must be passable
@fovPassability[[originalX,originalY]] = true
return false
end
#-----------------------------------------------------------------------------
# Guards and other "detector" events have an awareness of their immediate
# surroundings. If the player is adjacent to the event, they will be
# discovered if @allowDetectRadius is true. RETURNS: Boolean
# event: The guard that is currently checking for the player.
#-----------------------------------------------------------------------------
def playerInDetectionRadius(event)
return false unless @allowDetectRadius
adjacentTiles = [ [event.x, event.y - 1],
[event.x + 1, event.y - 1],
[event.x + 1, event.y],
[event.x + 1, event.y + 1],
[event.x, event.y + 1],
[event.x - 1, event.y + 1],
[event.x - 1, event.y],
[event.x - 1, event.y - 1] ]
for tile in adjacentTiles
if $game_player.x == tile[0] and $game_player.y == tile[1]
return true
end
end
return false
end
def playerOutOfRange(event)
direction = event.direction
case direction
when 2 #down
yDifference = event.y - $game_player.y
xDifference = event.x - $game_player.x
coneWidth = event.tunnelVision ? (@tunnelWidth / 2).floor : event.detectRange - 1
if yDifference > event.detectRange or $game_player.y < event.y - 1 or
(xDifference.abs > coneWidth)
return true
end
when 4 #left
yDifference = event.y - $game_player.y
xDifference = event.x - $game_player.x
coneWidth = event.tunnelVision ? (@tunnelWidth / 2).floor : event.detectRange - 1
if xDifference > event.detectRange or $game_player.x > event.x + 1 or
(yDifference.abs > coneWidth)
return true
end
when 6 #right
yDifference = $game_player.y - event.y
xDifference = $game_player.x - event.x
coneWidth = event.tunnelVision ? (@tunnelWidth / 2).floor : event.detectRange - 1
if xDifference > event.detectRange or $game_player.x < event.x - 1 or
(yDifference.abs > coneWidth)
return true
end
when 8 #up
yDifference = event.y - $game_player.y
xDifference = event.x - $game_player.x
coneWidth = event.tunnelVision ? (@tunnelWidth / 2).floor : event.detectRange - 1
if yDifference > event.detectRange or $game_player.y > event.y + 1 or
(xDifference.abs > coneWidth)
return true
end
end
return false
end
#-----------------------------------------------------------------------------
# Called in an event to enable detection for the event (guard).
# eventID: The id of the event. use @event_id in your event call!
# detectRange: How far away the guard can see
# detectAction: What happens when caught. defaults to game_over
# tunnelVision: Whether the guard has field of view or tunnel vision
# trueSight: Whether the guard can see through objects
#-----------------------------------------------------------------------------
def detector(eventID, detectRange, detectAction = 1,
tunnelVision = false, trueSight = false)
event = $game_map.events[eventID]
event.isAware = true
event.detectAction = detectAction
event.detectRange = detectRange
event.tunnelVision = tunnelVision
event.trueSight = trueSight
event.setPosition
end
#-----------------------------------------------------------------------------
# Called in an event to set the location tile of the "jail"
# eventID: The ID of the event that is at the jail location.
# Again, use @event_id
#-----------------------------------------------------------------------------
def jail(eventID)
event = $game_map.events[eventID]
@jailX = event.x
@jailY = event.y
end
#-----------------------------------------------------------------------------
# Executes the desired action upon detecting the player
#-----------------------------------------------------------------------------
def doDetectAction
case @detectingEvent.detectAction
when 0 # goto jail
sendToJail
when 1 # game over
$scene = Scene_Gameover.new
when 2 # execute common event
doCommonEvent
when 3 # MGS - enemy moves towards player position
if @detectingEvent.detectState == 2
if not @captured
case @captureAction
when 0
sendToJail
when 1, 3
gameOver
when 2
doCommonEvent
end
@captured = true
end
else
Sound.play_found
@detectingEvent.animation_id = 98
unless @alertedGuards.include?(@detectingEvent)
@alertedGuards.push(@detectingEvent)
end
end
end
$game_player.detected = false
@detectingEvent = nil
end
#-----------------------------------------------------------------------------
# MODE 0: Send player to the "jail" tile
#-----------------------------------------------------------------------------
def sendToJail
$game_player.transfer($game_map.map_id, @jailX, @jailY,
@detectingEvent.direction)
end
#-----------------------------------------------------------------------------
# MODE 1: Game Over
#-----------------------------------------------------------------------------
def gameOver
$scene = Scene_Gameover.new
end
#-----------------------------------------------------------------------------
# MODE 2: Execute Common Event
#-----------------------------------------------------------------------------
def doCommonEvent
$game_temp.common_event_id = @common_event_id
end
end
#
# Stealth Detection System v 1.0
# code by cmpsr2000 @ rpgrevolution.com/forums
# Released June 11, 2008
#
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
#
# SDS is an advanced stealth detection system for use in RPG Maker VX games.
# To use the system, you need to build an event that begins with a script call:
#
# $game_stealth.detector(eventID, detectRange, detectAction,
# tunnelVision, trueSight)
#
# eventID: The ID of the event. Use @event_id as it's the easiest way
# detectRange: How far the "guard" is able to see. Higher numbers require
# more CPU time to proccess! Lag increases exponentially if
# you are NOT using tunnel vision.
# detectAction: The action to perform when the guard detects the player
# DEFAULT = 1
# 0: Goto jail event
# 1: Game Over
# 2: Execute common event
# 3: MGS Mode
# tunnelVision: determines whether the guard's field of view cones out or
# goes in a straight line.
# DEFAULT = false
# trueSight: determines whether the guard can see through obstacles
# DEFAULT = false
#
# NOTE: You do NOT have to set parameters that have a default.
#
# MGS MODE EXPLAINATION:
#
# When using detectAction 3 (MGS Mode) guards will be "alerted" if
# they spot the player. The guard will run to the position they spotted
# the player at, then begin to look around for the player again. If the
# guard sees the player a second time, the captureAction is performed.
#
#-------------------------------------------------------------------------------
class Game_Stealth
attr_accessor :common_event_id
def initialize
@checkedTiles = []
@fovPassability = {}
@detectingEvent = nil
@tempInterpreter = nil
@alertedGuards = []
#---------------------------------------------------------------------------
# If you use tunnel vision, set the tunnel width here. It MUST be an ODD
# number or the game will round up to the next odd number! Widths over 3
# are not recomended.
#---------------------------------------------------------------------------
@tunnelWidth = 3
#---------------------------------------------------------------------------
# The common event ID to execute when using the common event detect action
#---------------------------------------------------------------------------
@common_event_id = 1
#---------------------------------------------------------------------------
# The speed and frequency the guards move when they detect the player in
# MGS mode.
# speed: 1: x8 slower,
# 2: x4 slower
# 3: x2 slower
# 4: normal
# 5: x2 faster
# 6: x4 faster
#
# frequency: 1: lowest
# 2: lower
# 3: normal
# 4: higher
# 5: highest
#---------------------------------------------------------------------------
@guardInterceptSpeed = 4
@guardInterceptFrequency = 6
#---------------------------------------------------------------------------
# When in MGS mode, the action to be performed if the guard detects the
# player a second time. This is set by default to game_over since all other
# options require some configuration. Valid values are:
# captureAction: 0: Goto jail event
# 1: Game Over
# 2: Execute common event
#---------------------------------------------------------------------------
@captureAction = 1
#---------------------------------------------------------------------------
# How many frames a guard will wait before executing each turn when
# searching for the player after detecting them in MGS mode.
#---------------------------------------------------------------------------
@waitTime = Graphics.frame_rate / 2 # 40
#---------------------------------------------------------------------------
# Change this to false to disable the detection radius around guards.
# This means the player can safely stand next to a guard as long as he's
# not in the guard's line of sight.
#---------------------------------------------------------------------------
@allowDetectRadius= true</FONT></P> <P><FONT size=6><FONT size=2> @tunnelWidth += 1 if (@tunnelWidth % 2) == 1
end
#-----------------------------------------------------------------------------
# Standard update method; checks for player detection then handles MGS
# state changes for guards then checks for player detection again
#-----------------------------------------------------------------------------
def update
if $game_player.detected and not $game_player.moving?
doDetectAction
end
for guard in @alertedGuards
case guard.detectState
when nil, 0 #alerted
guard.lock
guard.returnX = guard.x
guard.returnY = guard.y
guard.returnRoute = guard.move_route
guard.returnRouteIndex = guard.move_route_index
guard.returnSpeed = guard.move_speed
guard.returnFrequency = guard.move_frequency
guard.move_speed = @guardInterceptSpeed
guard.move_frequency = @guardInterceptFrequency
guard.isAware = false
guard.detectState = guard.goto($game_player.x, $game_player.y) ? 1 : 3
guard.unlock
when 1 #intercepting
if (guard.move_route_index == guard.move_route.list.length - 1 and
guard.stopping?) or guard.move_failed
guard.lock
guard.isAware = true
guard.spinInPlace(@waitTime)
guard.detectState = 2
guard.unlock
end
when 2 #checking
if guard.move_route_index == guard.move_route.list.length - 1 and
guard.stopping?
guard.lock
guard.isAware = false
go = guard.goto(guard.returnX, guard.returnY)
guard.detectState = 3
guard.unlock
end
when 3 #returning
if guard.move_route_index == guard.move_route.list.length - 1 and not
guard.moving?
guard.lock
guard.isAware = true
guard.move_route = guard.returnRoute
guard.move_route_index = guard.returnRouteIndex
guard.move_speed = guard.returnSpeed
guard.move_frequency = guard.returnFrequency
guard.detectState = 0
@captured = false
@alertedGuards.delete(guard)
guard.unlock
end
end
end
observe
end
#-----------------------------------------------------------------------------
# Runs through the aware events(guards) on the map and tells them to check
# for the player.
#-----------------------------------------------------------------------------
def observe
for event in $game_map.events.values #events is a hash!
if event.isAware
if event.moved? or event.turned? or $game_player.moved?
if checkForDetection(event)
$game_player.detected = true
@detectingEvent = event
return
end
end
end
@checkedTiles = []
@fovPassability = {}
end
end
#-----------------------------------------------------------------------------
# Runs through each tile in the event(guard)'s field of view and checks
# it for the player. RETURNS: Boolean
# event: The guard that is currently checking for the player.
#-----------------------------------------------------------------------------
def checkForDetection(event)
return true if playerInDetectionRadius(event)
return false if playerOutOfRange(event)
fieldOfView = event.tunnelVision ? @tunnelWidth : 1
for distance in 1..event.detectRange
for width in 1..fieldOfView
return true if checkTile(event, width, distance)
end
fieldOfView += 2 unless event.tunnelVision
end
return false
end
#-----------------------------------------------------------------------------
# Checks a tile for the player. RETURNS: Boolean
# event: The guard that is currently checking for the player.
# width: The width-index of the tile being checked
# distance: The depth-index of the tile being checked
#-----------------------------------------------------------------------------
def checkTile(event, width, distance)
if event.tunnelVision
if @tunnelWidth > 1
width = width - ((@tunnelWidth - 1)/2)
else
width = 0
end
else
width -= distance
end
x = event.x
y = event.y
direction = event.direction
case direction
when 2
y += distance
x += width
when 4
y += width
x -= distance
when 6
y += width
x += distance
when 8
y -= distance
x += width
end
return false if @checkedTiles.index([x,y]) != nil
return false if tileHidden?(event, x, y)
return true if $game_player.x == x and $game_player.y == y
return false
end
#-----------------------------------------------------------------------------
# Determines the visibility of a tile to a guard. RETURNS: Boolean
# event: The guard that is currently checking for the player.
# x: The x coordinate of the tile to be checked
# y: The y coordinate of the tile to be checked
#-----------------------------------------------------------------------------
def tileHidden?(event, x, y)
return false if event.trueSight
#check the current tile first
@checkedTiles.push([x,y])
originalX = x
originalY = y
if not $game_map.passable?(x, y, 0)
@fovPassability[[x,y]] = false
return true
end
#now check all adjacent tiles one step closer to the detector
direction = event.direction
case direction
when 2 #down
y -= 1
for i in (x-1)..(x+1)
#if we've checked the tile, then it exists in the FoV
if @checkedTiles.index([i,y]) != nil
if not @fovPassability[[i,y]]
@fovPassability[[originalX,originalY]] = false
return true
end
end
end
when 4 #left
x += 1
for i in (y-1)..(y+1)
#if we've checked the tile, then it exists in the FoV
if @checkedTiles.index([x,i]) != nil
if not @fovPassability[[x,i]]
@fovPassability[[originalX,originalY]] = false
return true
end
end
end
when 6 #right
x -= 1
for i in (y-1)..(y+1)
#if we've checked the tile, then it exists in the FoV
if @checkedTiles.index([x,i]) != nil
if not @fovPassability[[x,i]]
@fovPassability[[originalX,originalY]] = false
return true
end
end
end
when 8 #up
y += 1
for i in (x-1)..(x+1)
#if we've checked the tile, then it exists in the FoV
if @checkedTiles.index([i,y]) != nil
if not @fovPassability[[i,y]]
@fovPassability[[originalX,originalY]] = false
return true
end
end
end
end
#this tile and the blocking tiles must be passable
@fovPassability[[originalX,originalY]] = true
return false
end
#-----------------------------------------------------------------------------
# Guards and other "detector" events have an awareness of their immediate
# surroundings. If the player is adjacent to the event, they will be
# discovered if @allowDetectRadius is true. RETURNS: Boolean
# event: The guard that is currently checking for the player.
#-----------------------------------------------------------------------------
def playerInDetectionRadius(event)
return false unless @allowDetectRadius
adjacentTiles = [ [event.x, event.y - 1],
[event.x + 1, event.y - 1],
[event.x + 1, event.y],
[event.x + 1, event.y + 1],
[event.x, event.y + 1],
[event.x - 1, event.y + 1],
[event.x - 1, event.y],
[event.x - 1, event.y - 1] ]
for tile in adjacentTiles
if $game_player.x == tile[0] and $game_player.y == tile[1]
return true
end
end
return false
end
def playerOutOfRange(event)
direction = event.direction
case direction
when 2 #down
yDifference = event.y - $game_player.y
xDifference = event.x - $game_player.x
coneWidth = event.tunnelVision ? (@tunnelWidth / 2).floor : event.detectRange - 1
if yDifference > event.detectRange or $game_player.y < event.y - 1 or
(xDifference.abs > coneWidth)
return true
end
when 4 #left
yDifference = event.y - $game_player.y
xDifference = event.x - $game_player.x
coneWidth = event.tunnelVision ? (@tunnelWidth / 2).floor : event.detectRange - 1
if xDifference > event.detectRange or $game_player.x > event.x + 1 or
(yDifference.abs > coneWidth)
return true
end
when 6 #right
yDifference = $game_player.y - event.y
xDifference = $game_player.x - event.x
coneWidth = event.tunnelVision ? (@tunnelWidth / 2).floor : event.detectRange - 1
if xDifference > event.detectRange or $game_player.x < event.x - 1 or
(yDifference.abs > coneWidth)
return true
end
when 8 #up
yDifference = event.y - $game_player.y
xDifference = event.x - $game_player.x
coneWidth = event.tunnelVision ? (@tunnelWidth / 2).floor : event.detectRange - 1
if yDifference > event.detectRange or $game_player.y > event.y + 1 or
(xDifference.abs > coneWidth)
return true
end
end
return false
end
#-----------------------------------------------------------------------------
# Called in an event to enable detection for the event (guard).
# eventID: The id of the event. use @event_id in your event call!
# detectRange: How far away the guard can see
# detectAction: What happens when caught. defaults to game_over
# tunnelVision: Whether the guard has field of view or tunnel vision
# trueSight: Whether the guard can see through objects
#-----------------------------------------------------------------------------
def detector(eventID, detectRange, detectAction = 1,
tunnelVision = false, trueSight = false)
event = $game_map.events[eventID]
event.isAware = true
event.detectAction = detectAction
event.detectRange = detectRange
event.tunnelVision = tunnelVision
event.trueSight = trueSight
event.setPosition
end
#-----------------------------------------------------------------------------
# Called in an event to set the location tile of the "jail"
# eventID: The ID of the event that is at the jail location.
# Again, use @event_id
#-----------------------------------------------------------------------------
def jail(eventID)
event = $game_map.events[eventID]
@jailX = event.x
@jailY = event.y
end
#-----------------------------------------------------------------------------
# Executes the desired action upon detecting the player
#-----------------------------------------------------------------------------
def doDetectAction
case @detectingEvent.detectAction
when 0 # goto jail
sendToJail
when 1 # game over
$scene = Scene_Gameover.new
when 2 # execute common event
doCommonEvent
when 3 # MGS - enemy moves towards player position
if @detectingEvent.detectState == 2
if not @captured
case @captureAction
when 0
sendToJail
when 1, 3
gameOver
when 2
doCommonEvent
end
@captured = true
end
else
Sound.play_found
@detectingEvent.animation_id = 98
unless @alertedGuards.include?(@detectingEvent)
@alertedGuards.push(@detectingEvent)
end
end
end
$game_player.detected = false
@detectingEvent = nil
end
#-----------------------------------------------------------------------------
# MODE 0: Send player to the "jail" tile
#-----------------------------------------------------------------------------
def sendToJail
$game_player.transfer($game_map.map_id, @jailX, @jailY,
@detectingEvent.direction)
end
#-----------------------------------------------------------------------------
# MODE 1: Game Over
#-----------------------------------------------------------------------------
def gameOver
$scene = Scene_Gameover.new
end
#-----------------------------------------------------------------------------
# MODE 2: Execute Common Event
#-----------------------------------------------------------------------------
def doCommonEvent
$game_temp.common_event_id = @common_event_id
end
end
Demo: SDS DEMO for XP
You'll also need this sound if you didn't download the demo: Click to view attachment
Customization
For customization, go to the original thread - Stealth Detection System for VX. As customizing the script is the same for both systems.
Compatibility
Give this a shot, I haven't tested it extensively, so there's almost guaranteed to be at least 999 bugs, but I tried re-enacting all the default maps, and I didn't notice anything odd, oh well, give it a shot, and let me know
As you can see it's still kind of in the beta stage for XP, so if obviously it might run into some areas with some other scripts that Redefines Scene_Title, Scene_Map and Game_Event.
Screenshot
Not yet!
FAQ
Not yet!
Terms and Conditions
Feel free to use this in your game, but please give me a shout in the credits! Also, feel free to edit this for your own personal purposes but please do not repost an edited version without my consent first.
Thanks ^^/
Special Thanks
Cmrp2000 - for creating this script
Night_Runner - for converting it to XP
