Advertisement
QuasiXi

Quasi Sight

Oct 5th, 2014
459
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 13.44 KB | None | 0 0
  1. #==============================================================================
  2. # ** Quasi Sight v0.8
  3. #  Requires Module Quasi and Quasi Movement
  4. #   http://quasixi.wordpress.com/2014/09/23/quasi/
  5. #   http://quasixi.wordpress.com/2014/09/23/quasi-movement/
  6. # (Make sure they're up to date)
  7. #==============================================================================
  8. #  A true line of sight for events.  Events can not see through impassible
  9. # tiles (Regions that are set inside Quasi Movement script) unless the region
  10. # is set to be able to see over in the module below.  Events also can not see
  11. # through other events unless they are marked Through or have the comment
  12. # <seethrough>
  13. #==============================================================================
  14. # Change Log
  15. #------------------------------------------------------------------------------
  16. # v0.8 - Released
  17. #------------------------------------------------------------------------------
  18. # To do / Upcoming
  19. #------------------------------------------------------------------------------
  20. # - Make a method for the event sight checking, so it doesn't require as much
  21. #   eventing (Even though it is not much)
  22. # - Simplify algorithm even more, was already shortend 120 lines.
  23. # - Write a guide?
  24. #==============================================================================
  25. module Quasi
  26.   module Sight
  27. #------------------------------------------------------------------------------
  28. # Instructions:
  29. #  Step 1. Set SEERGIONS to a list of impassible regions you want to be able to
  30. #          see over.  Example region 44 is the same as region 54 inside
  31. #          Quasi Movement, but you can see over 44 which can be used for
  32. #          holes/water tiles.
  33. #------------------------------------------------------------------------------
  34.     SEEREGIONS = [
  35.       44,
  36.     ]
  37. #------------------------------------------------------------------------------
  38. #  Step 2. Methods
  39. #          To run a check if someone is in sight you do:
  40. #             obj.sight(range, who, shape)
  41. #          *obj is usually the event but it can be the game player
  42. #            - for event it will be $game_map.events[ID NUMBER HERE]
  43. #          *range is the number of tiles (32x32 tiles) the obj can see
  44. #            - default is 6
  45. #          *who is who are you checking if the obj can see?
  46. #            - most cases you will want to check if the event can see the player
  47. #              so who would be $game_player
  48. #            - default is $game_player
  49. #          *shape (don't use, it's incomplete, only 1 shape exists)
  50. #    Examples:
  51. #          $game_map.events[1].sight(6, $game_player)
  52. #             Will return true if event 1 can see the game player or false if it
  53. #            can not.
  54. #          $game_map.events[1].sight
  55. #             Exactly the same as previous example since 6 is default range
  56. #            and $game_player is default who.
  57. #
  58. #    Be aware you don't want to constantly keep calling them, like in an parallel
  59. #   event, specially if the events are moving, or it can cause lag.  The method
  60. #   benchmarked 0.003 to 0.005 in testing for 1 event, with 10 it was around
  61. #   0.07 to 1.00, but that was from previous algrithm, changed it up so it should
  62. #   be a lot faster.
  63. #    So I added a new method which you can wrap it around the previous
  64. #         obj.update_sight?
  65. #             it will return true if that obj's x, y or direction has changed
  66. #            since the last call, if not it returns false.
  67. #     But this probably only makes it so calls every couple of frames instead of
  68. #    every frame.
  69. #------------------------------------------------------------------------------
  70.   end
  71. end
  72. #==============================================================================#
  73. # By Quasi (http://quasixi.wordpress.com/)
  74. #  - 10/3/14
  75. #==============================================================================#
  76. #   ** Stop! Do not edit anything below, unless you know what you      **
  77. #   ** are doing!                                                      **
  78. #==============================================================================#
  79. $imported = {} if $imported.nil?
  80. $imported["Quasi_Sight"] = 0.8
  81.  
  82. if $imported["Quasi"] >= 0.2 && $imported["Quasi_Movement"] >= 0.85
  83. #==============================================================================
  84. # ** Game_Character
  85. #------------------------------------------------------------------------------
  86. #  A character class with mainly movement route and other such processing
  87. # added. It is used as a super class of Game_Player, Game_Follower,
  88. # GameVehicle, and Game_Event.
  89. #==============================================================================
  90.  
  91. class Game_Character < Game_CharacterBase
  92.   alias qs_init init_public_members
  93.   #--------------------------------------------------------------------------
  94.   # * Initialize Public Member Variables
  95.   #--------------------------------------------------------------------------
  96.   def init_public_members
  97.     qs_init
  98.     @last_pos = [0,0]
  99.     @last_dir = 0
  100.   end
  101.  
  102.   def update_sight?
  103.     if @last_pos != [(@px/32.0).truncate, (@py/32.0).truncate] || @last_dir != @direction
  104.       @last_pos = [(@px/32.0).truncate, (@py/32.0).truncate]
  105.       @last_dir = @direction
  106.       return true
  107.     end
  108.     return false
  109.   end
  110.   #--------------------------------------------------------------------------
  111.   # * Checks event's range
  112.   # Use angles
  113.   # radian = angle/180 * pi
  114.   # hypot = adj / cos(radian)
  115.   # opp   = hypot * sin(radian)
  116.   #--------------------------------------------------------------------------
  117.   def sight(range=6, obj=$game_player, shape=:triangle)
  118.     x1 = @x
  119.     y1 = @y
  120.     case shape
  121.     when :triangle
  122.       dir = @direction
  123.       if (dir == 2 || dir == 8)
  124.         range = [(obj.y.round-y1).truncate.abs,range].min
  125.         range = 1 if range == 0
  126.         x2 = x1 - range
  127.         x3 = x1 + range
  128.         y2 = y1 + (dir == 2 ? range : -range)
  129.         y3 = y2
  130.       else
  131.         range = [(obj.x.round-x1).truncate.abs,range].min
  132.         range = 1 if range == 0
  133.         y2 = y1 - range
  134.         y3 = y1 + range
  135.         x2 = x1 + (dir == 6 ? range : -range)
  136.         x3 = x2
  137.       end
  138.      
  139.       rays = [[x1,y1,x2,y2],[x1,y1,x3,y3]]
  140.       area = ray_cast(rays)
  141.       # Check if inside area
  142.       return false if !inside_area(area, obj)
  143.       # Check Events
  144.       shadows = $game_map.events.values.select do |event|
  145.         next if event == self
  146.         next if event.see_through || event.through
  147.         next if event.x.truncate == @x.truncate+range ||
  148.                 event.y.truncate == @y.truncate+range
  149.         inside_area(area, event)
  150.       end
  151.       shadows.each do |event|
  152.         return false if inside_area(shadow_cast(event, range), obj)
  153.       end
  154.       # Check regions
  155.       return false if region_inside_area(obj, area, range)
  156.       return true
  157.     end
  158.     false
  159.   end
  160.   #--------------------------------------------------------------------------
  161.   # * Makes area from 2 lines
  162.   #--------------------------------------------------------------------------
  163.   def ray_cast(rays)
  164.     area = []
  165.    
  166.     rays.each_with_index do |line, i|
  167.       xi = line[0].round; yi = line[1].round
  168.       xf = line[2].round; yf = line[3].round
  169.       xdif = xi - xf
  170.       ydif = yi - yf
  171.       x1 = xi < xf ? xi : xf
  172.       x2 = x1 == xi ? xf : xi
  173.       y1 = yi < yf ? yi : yf
  174.       y2 = y1 == yi ? yf : yi
  175.       slope = ydif.to_f / xdif if xdif != 0
  176.       area[i] = [] if area[i].nil?
  177.      
  178.       for y in y1..y2
  179.         for x in x1..x2
  180.           next if area[i].include?([x,y])
  181.           if slope == 0
  182.             next unless y == y1
  183.             area[i] << [x,y]
  184.           elsif slope.nil?
  185.             next unless x == x1
  186.             area[i] << [x,y]
  187.           else
  188.             next unless (y-yi).abs.truncate == ((x-xi)*slope).abs.truncate ||
  189.               (x-xi).abs.truncate == ((y-yi)/slope).abs.truncate
  190.             area[i] << [x,y]
  191.           end
  192.         end
  193.       end
  194.     end
  195.    
  196.     area.each_with_index do |line, i|
  197.       line.reverse! if line[0] != [rays[i][0],rays[i][1]]
  198.     end
  199.     return area
  200.   end
  201.   #--------------------------------------------------------------------------
  202.   # * Gets lines for shadows
  203.   #--------------------------------------------------------------------------
  204.   def shadow_cast(obj, range, reg=false)
  205.     vertices = obj
  206.     vertices = obj.vertices if reg == false
  207.     x = v_center[0]; y = v_center[1]
  208.    
  209.     angle_tl = Math.angle([x,-y],[vertices[0][0],-vertices[0][1]])
  210.     angle_tr = Math.angle([x,-y],[vertices[1][0],-vertices[1][1]])
  211.     angle_bl = Math.angle([x,-y],[vertices[2][0],-vertices[2][1]])
  212.     angle_br = Math.angle([x,-y],[vertices[3][0],-vertices[3][1]])
  213.    
  214.     angle_hash = {
  215.     angle_tl => vertices[0], angle_tr => vertices[1],
  216.     angle_bl => vertices[2], angle_br => vertices[3]
  217.     }
  218.    
  219.     angle1 = angle_hash.select{|k, v| k == angle_hash.keys.max}
  220.     angle1 = angle1.values.flatten
  221.     angle2 = angle_hash.select{|k, v| k == angle_hash.keys.min}
  222.     angle2 = angle2.values.flatten
  223.    
  224.     slope1 = (y-angle1[1])/(angle1[0]-x) if (angle1[0]-x) != 0
  225.     slope2 = (y-angle2[1])/(angle2[0]-x) if (angle2[0]-x) != 0
  226.     range = (@direction == 2 || @direction == 6) ? range : -range
  227.     if @direction == 2 || @direction == 8
  228.       v1 = v2 = x
  229.       v1 = (((range*32)+y-angle1[1])/slope1) - angle1[0] if slope1 != 0
  230.       v2 = (((range*32)+y-angle2[1])/slope2) - angle2[0] if slope2 != 0
  231.       v1 = x < angle1[0] ? x+range.abs : x-range.abs if slope1 == 0
  232.       v2 = x < angle2[0] ? x+range.abs : x-range.abs if slope2 == 0
  233.     else
  234.       # This formula is sightly incorrect, might works in most cases
  235.       # as long as the event is at the end of the range. if it is
  236.       # it makes a big shadow instead.
  237.       v1 = y < angle1[1] ? y+range.abs : y-range.abs
  238.       v2 = y < angle2[1] ? y+range.abs : y-range.abs
  239.       v1 = (((range*32)+x-angle1[0])*slope1) - angle1[1] if slope1 != nil
  240.       v2 = (((range*32)+x-angle2[0])*slope2) - angle2[1] if slope2 != nil
  241.       v1 = 0 if slope1 == 0
  242.       v2 = 0 if slope2 == 0
  243.     end
  244.     p1 = (v1/32.0).truncate
  245.     p2 = (v2/32.0).truncate
  246.    
  247.     p3 = p1.abs > p2.abs ? p2.abs : p1.abs
  248.     p4 = p3 == p1.abs ? p2.abs : p1.abs
  249.    
  250.    
  251.     if reg == false
  252.       if @direction == 2 || @direction == 8
  253.         rays = [[obj.x, obj.y, p3, @y+range], [obj.x, obj.y, p4, @y+range]]
  254.       else
  255.         rays = [[obj.x, obj.y, @x+range, p3], [obj.x, obj.y, @x+range, p4]]
  256.       end
  257.     else
  258.       if @direction == 2 || @direction == 8
  259.         rays = [[reg[0], reg[1], p3, @y+range], [reg[0], reg[1], p4, @y+range]]
  260.       else
  261.         rays = [[reg[0], reg[1], @x+range, p3], [reg[0], reg[1], @x+range, p4]]
  262.       end
  263.     end
  264.     return ray_cast(rays)
  265.   end
  266.   #--------------------------------------------------------------------------
  267.   # * Checks if obj is inside area
  268.   #--------------------------------------------------------------------------
  269.   def inside_area(area, obj)
  270.     passed1 = false; passed2 = false
  271.     if (@direction == 2 || @direction == 8)
  272.       area[0].each do |left|
  273.         next unless obj.x.round >= left[0] && obj.y.round == left[1]
  274.         passed1 = true
  275.       end
  276.       area[1].each do |right|
  277.         next unless obj.x.round<= right[0] && obj.y.round == right[1]
  278.         passed2 = true
  279.       end
  280.     else
  281.       area[0].each do |top|
  282.         next unless obj.y.round >= top[1] && obj.x.round == top[0]
  283.         passed1 = true
  284.       end
  285.       area[1].each do |bot|
  286.         next unless obj.y.round <= bot[1] && obj.x.round == bot[0]
  287.         passed2 = true
  288.       end
  289.     end
  290.     return passed1 && passed2
  291.   end
  292.   #--------------------------------------------------------------------------
  293.   # * Checks regions inside area
  294.   #--------------------------------------------------------------------------
  295.   def region_inside_area(obj,area,range)
  296.     field = []
  297.     regions = []
  298.     rbox = Quasi::Movement::REGIONBOXES.keys
  299.     for i in 0..range
  300.       field << [area[0][i][0]..area[1][i][0],area[0][i][1]..area[1][i][1]]
  301.     end
  302.     field.each do |f|
  303.       for y in f[1]
  304.         for x in f[0]
  305.           next unless rbox.include?($game_map.region_id(x,y))
  306.           next if Quasi::Sight::SEEREGIONS.include?($game_map.region_id(x,y))
  307.           next if x == @x.truncate+range || y == @y.truncate+range
  308.           next if x == @x.truncate-range || y == @y.truncate-range
  309.           regions << [x,y]
  310.         end
  311.       end
  312.     end
  313.    
  314.     regions.each do |reg|
  315.       rb = regbox(reg[0],reg[1])
  316.       if rb[0].is_a?(Array)
  317.         rb.each do |box|
  318.           tl = [box[0].first,box[1].first]
  319.           tr = [box[0].last,box[1].first]
  320.           bl = [box[0].first,box[1].last]
  321.           br = [box[0].last,box[1].last]
  322.           return true if inside_area(shadow_cast([tl, tr, bl, br], range, reg), obj)
  323.         end
  324.       else
  325.         tl = [rb[0].first,rb[1].first]
  326.         tr = [rb[0].last,rb[1].first]
  327.         bl = [rb[0].first,rb[1].last]
  328.         br = [rb[0].last,rb[1].last]
  329.         return true if inside_area(shadow_cast([tl, tr, bl, br], range, reg), obj)
  330.       end
  331.     end
  332.     return false
  333.   end
  334.   #--------------------------------------------------------------------------
  335.   # * See Through
  336.   #--------------------------------------------------------------------------
  337.   def see_through
  338.     grab_comment(/(?i:<seethrough>)/)
  339.   end
  340. end
  341. else
  342.   msgbox(sprintf("[Quasi Sight] Requires Quasi module + Quasi Movement to run."))
  343. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement