Scripts:Line of sight

From OHRRPGCE-Wiki

This is a line-of-sight script which checks whether one or more NPCs can see the player. By default an NPC which spots the player is changed to movetype "chase"; edit the "vigilant guard" script to change the effect. It supports obstructions/walls (marked with a zone) and the sight range depends on the angle to the player: if the player is in a 90 degree segment in front of the guard, then the range in tiles is given by the "forward view distance" variable. If the player is to the side or behind the guard, then the range is given by the "rear view distance" variable.

To use the script, set "vigilant guards loop" as the map autorun script on a map, and edit it to add one call to "vigilant guard" for each guard on the map. (If you use the default chase-on-sight behaviour then you will probably want separate NPC definitions (with different IDs but otherwise identical) for each guard, because all NPCs with the same ID will be set to chase at once.) Mark all the walls on the map which should obstruct line-of-sight with a zone, and set the "zone:LOS obstruct" constant to the right zone ID.

There is also an optional debug key which highlights all the tiles currently in view of a guard. To use it, set "onkeypress" as the map on-key-press script and set the constants "tile:LOS debug" to a layer number and "layer:LOS debug" to a tile number to use to draw the debug view.

Other options[edit]

A much simpler script which only looks directly ahead of the player and doesn't support walls is here. It can be easily modified to support walls too (some possibly useful discussion is here).

Field of View, aka shadow casting, is different from line of sight. A discussion of field of view in OHR games can be found here.

The scripts[edit]

global variable, begin
  100, debug guard LOS        # Whether we're debugging
  101, forward view distance  # Distance in tiles guards can see forward
  102, rear view distance     # Distance they can see in other directions
end

define constant, begin
  1, zone:LOS obstruct   # ID of zone which marks tiles which can't be seen past
  1, layer:LOS debug     # Number of map layer to draw to 
  1, tile:LOS debug      # Which tile to draw on the LOS debug map layer. Anything other than 0
end


plotscript, new game, begin
  forward view distance := 6
  rear view distance := 3
  vigilant guards loop
end

# Optional on-key-press script which adds a debug key
plotscript, onkeypress, begin
  # Press D to toggle LOS debugging
  if (keyval (key: d) >> 1) then (
    clear debug map
    if (debug guard LOS) then (
      debug guard LOS := false
    ) else (
      debug guard LOS := true
    )
  )
end

plotscript, vigilant guards loop, begin
  variable (this map)

  this map := current map

  while (this map == current map) do, begin
    if (debug guard LOS) then (clear debug map)

    vigilant guard (0)  #change '0' to the correct NPC ID
    #vigilant guard (1) #repeat as desired

    wait (1)
  end
end


# Clears the LOS debug map layer around the camera
script, clear debug map, begin
  variable (cam x, cam y, x, y)
  cam x := camera pixel x / 20
  cam y := camera pixel y / 20
  for (y, cam y -- 2, cam y + 11) do (
    for (x, cam x -- 2, cam x + 17) do (
      # Don't draw off map edge
      if (x << 0 || x >= map width || y << 0 || y >= map height) then (continue)

      write map block (x, y, 0, layer:LOS debug)
    )
  )
end

script, draw debug map, x1, y1, guard dir, begin
  variable (center cam x, center cam y, x2, y2)
  center cam x := (camera pixel x + 160) / 20
  center cam y := (camera pixel y + 100) / 20

  for (y2, y1 -- forward view distance, y1 + forward view distance) do (
    # Don't draw off screen or map edge 
    if (abs(y2 -- center cam y) >> 5 || y2 << 0 || y2 >= map height) then (continue)

    for (x2, x1 -- forward view distance, x1 + forward view distance) do (
      # Don't draw off screen or map edge
      if (abs(x2 -- center cam x) >> 8 || x2 << 0 || x2 >= map width) then (continue)

      if (check line of sight (x1, y1, x2, y2, guard dir)) then (
        write map block (x2, y2, tile:LOS debug, layer:LOS debug)
      )
    )
  )
end

# Check whether this guard can see the hero
script, vigilant guard, NPC ID, begin
  variable (guard)
  guard := NPC reference (NPC ID)

  if (debug guard LOS) then (draw debug map (NPC X (guard), NPC Y (guard), NPC direction (guard)))

  if (check line of sight (NPC X (guard), NPC Y (guard), hero X (me), hero Y (me), NPC direction (guard))) then (
    # Customise this
    alter NPC (NPC ID, NPCstat:move type, NPCmovetype:chaseyou)
    alter NPC (NPC ID, NPCstat:move speed, 5)
  )
end

# Check whether tile #2 is in view from tile #1, when facing a certain direction.
# Returns true or false.
script, check line of sight, x1, y1, x2, y2, guard dir, begin
  variable (distx, disty, hero dir)
  distx := x2 -- x1
  disty := y2 -- y1

  # Special check to prevent divide-by-zero
  if (distx == 0 && disty == 0) then (exit returning (true))

  # Figure out in which direction from the guard the hero is
  # (The result is -1 if they are diagonal)

  hero dir := -1
  if (disty << 0 && abs(distx) << abs(disty)) then (hero dir := up)
  if (disty >> 0 && abs(distx) << abs(disty)) then (hero dir := down)
  if (distx << 0 && abs(disty) << abs(distx)) then (hero dir := left)
  if (distx >> 0 && abs(disty) << abs(distx)) then (hero dir := right)

  # How far can the guard see in this direction?
  variable (view dist)
  if (guard dir == hero dir) then (
    view dist := forward view distance
  ) else (
    view dist := rear view distance
  )

  # If the hero is too far away, stop. (Pythagoras)
  # We add half a tile for smoother circles, then multiply both sides by 4
  if (4 * distx^2 + 4 * disty^2 >> (2 * view dist + 1)^2) then (exit returning (false))

  # Now check for view obstructions

  # x100 and y100 are x and y measured in 100ths of a tile
  variable (x100, y100, x100 step, y100 step, num steps, i)
  if (abs(distx) >> abs(disty)) then (
    # Step one tile in X direction and a fraction of a tile in Y direction at a time
    # (The --1 is a trick to prevent asymmetrical fields of view. Difficult to explain)
    x100 step := sign(distx) * 100
    y100 step := sign(disty) * (abs(100 * disty / distx) -- 1)
    num steps := abs(distx)
  ) else (
    # Step one tile in Y direction and a fraction of a tile in X direction at a time
    x100 step := sign(distx) * (abs(100 * distx / disty) -- 1)
    y100 step := sign(disty) * 100
    num steps := abs(disty)
  )

  # Walk from the hero back to the guard. We check the tile that the hero is standing on,
  # but not the one the guard is standing on
  x100 := x2 * 100
  y100 := y2 * 100
  for (i, 1, num steps) do (
    # (x100 + 50) / 100 rounds to the nearest tile
    if (read zone (zone:LOS obstruct, (x100 + 50) / 100, (y100 + 50) / 100) <> 0) then (exit returning (false))

    x100 -= x100 step
    y100 -= y100 step
  )

  # If we get here, the hero is in view
  return (true)
end