Advertisement
BadMinotaur

Beacons - NPC Spawning, by BadMinotaur

Sep 24th, 2015
193
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 16.83 KB | None | 0 0
  1. =begin
  2.  
  3. BEACONS - NPC SPAWNING
  4. v0.9 by BadMinotaur
  5.  
  6. CHANGELOG
  7. ==========
  8. 09/26/15 - Release. Also updated terms of use.
  9.  
  10. HOW TO INSTALL THIS SCRIPT
  11. ===========================
  12. Paste this script in the RPG Maker Script Editor, beneath "Materials" and above
  13. "Main Process." There are also two configuration options you'll need to set,
  14. explained below.
  15.  
  16. COMPATIBILITY
  17. ==============
  18. This script aliases, but does not overwrite, Game_Map::setup and
  19. Game_Event::initialize. Any other scripts that modify these two methods have
  20. a chance at being incompatible with them. If this script breaks something in
  21. one of those scripts, try placing it before those in the Script Editor.
  22. If this script has a glaring incompatibility with another popular script,
  23. please let me know in the forums! I'll see what I can do.
  24.  
  25. TERMS OF USE
  26. =============
  27. You may not redistribute this script without my written permission.
  28.  
  29. You may freely use this script in non-commercial projects.
  30. You must contact me before use in commercial projects. Don't worry, I'm a
  31. reasonable fellow.
  32. You may alter and add on to this script for your own projects. You may not
  33. redistribute this script as an altered script (in other words, making a change
  34. does not mean you get to ignore the "don't redistribute" clause above).
  35. However, you may freely create and redistribute any patches to the script, so
  36. long as said patches don't unnecessarily duplicate any major functionality of
  37. the script.
  38.  
  39. If you use my script, I'd like credit. "BadMinotaur" will do =)
  40.  
  41. HOW TO USE BEACONS
  42. ===================
  43. First, you need to set up a container map. This is a map that contains all of
  44. the events you'll want to spawn. First, make a new, empty map, and make a note
  45. of its number. Don't worry about naming it anything specific -- you can name it
  46. and really do anything you want with it -- this script only cares about events
  47. on this map.
  48.  
  49. Open up this script in your script editor, and navigate to the MAP_ID and
  50. MAP_NAME options, and change them to whatever number your map has.
  51.  
  52. NOTE: Please read the instructions on how to find the MAP_ID and the MAP_NAME!
  53. Most importantly, MAP_NAME is *not* the name you give it inside of RPG Maker!
  54. It is the file name of the map, without the .rvdata2 extension.
  55.  
  56. Now we get to actually make the events we will spawn! Make an event like
  57. normal. When you're satisfied with your event, add a comment at the very top of
  58. the event. Here, we're going to "tag" the event. Type this:
  59.  
  60.   <beacontype: my_beacon_name>
  61.  
  62. into the comment. This will let the script know that you now have an NPC named
  63. "my_beacon_name" that you want to spawn later!
  64.  
  65. The great thing about this script is that more than one NPC can have the same
  66. beacontype. If you gave three different NPCs the beacontype "my_beacon_name",
  67. the script would choose randomly between each one when spawning them. This is
  68. great for, say, spawning a town of NPCs that change each time the map loads!
  69.  
  70. There is one more tag you can put on these events. By default, this script
  71. assumes each NPC you're spawning is "generic" -- any number of them can spawn.
  72. You can instead put another tag next to your beacontype:
  73.  
  74.   <unique>
  75.  
  76. This tag tells the script that if the current NPC is already on the map, STOP
  77. the spawning! This NPC will only be one per map, no matter what. Great for
  78. story-specific items or NPCs.
  79.  
  80. NOTE: This script checks each *current* page for beacontypes! What this means
  81. is that if you set a variable/switch that makes your NPC change pages, this
  82. script will look at the new page to see if it has any beacontypes. So if you
  83. want the NPC to keep its beacontypes (and uniqueness) you'll need to copy and
  84. paste them onto each relevant page!
  85.  
  86. After you've set up whatever NPCs you want, then you can actually spawn them on
  87. a map! This is also done using event tags. On whatever map you want an NPC to
  88. spawn, just add a blank, empty event with the following tag (again, in a
  89. comment at the top of the event):
  90.  
  91.   <beacon: my_beacon_name>
  92.  
  93. Replacing "my_beacon_name" with whatever name you gave your NPC on the
  94. container map.
  95.  
  96. The cool thing about beacons is that the beacon also looks at what page it is
  97. on to see what beacontype it will spawn. So, for example, if you put a bunch
  98. of beacons in a town to spawn citizens, then that town gets taken over by
  99. goblins, you can set a switch and all of the beacons will now spawn goblins!
  100.  
  101. There is one last bit of functionality when using beacons. If you'd like a
  102. beacon to sometimes spawn a "rare" NPC some percent of the time, tag it like
  103. this:
  104.  
  105.   <rare: another_beacon_name>
  106.  
  107. Then, the spawner will roll a 100-sided die to see if the rare beacon is
  108. spawned. The default chance for a rare NPC to spawn from a raretype is 25%, but
  109. you can change this number with the BEACON_RARE_CHANCE option below.
  110.  
  111. NOTE: I say to use an empty event for your beacon, but you don't have to. You
  112. can give it a graphic, and even other event commands, but none of these will
  113. matter or show up in-game. When the game sees that it is a beacon, it will
  114. destroy the original event and replace it with the spawned NPC.
  115.  
  116. IF YOU'RE A SCRIPTER (or want to use the "Script Call" event command)
  117. ======================================================================
  118. This script has a goodie for you too. I've tried to make spawning NPCs
  119. dynamically, outside of the beacon system, easy to access. Here's the command:
  120.  
  121.   NPCSpawner.spawn_beacontype(beacon_name, x, y)
  122.     or
  123.   NPCSpawner.spawn_beacontype_with_rare(beacon_name, rare_name, x, y)
  124.  
  125. As long as beacon_name and rare_name correspond to a beacontype you've defined
  126. in your container map, you're clear to go.
  127.  
  128.  *******!!BIG CAVEAT!!*******
  129. ==============================
  130. As of right now, self-switches on NPCs in the container map DO NOT PERSIST. In
  131. other words, if you spawn an NPC on a map and change its self-switch, the next
  132. time it would spawn that NPC, all of its self-switches will be set to OFF! I
  133. plan to introduce this functionality in a later update to the script, but right
  134. now don't plan on your NPCs keeping self-switches on.
  135.  
  136. A QUIRK IN UNIQUE NPCS
  137. =======================
  138. The way the script detects for unique NPCs is very simple, but it can cause
  139. trouble if you reuse a lot of resources. First, it checks the name of the
  140. graphic file that the unique NPC is using. Then it checks the index of the
  141. graphic file that the unique NPC is using. If both of these match any other
  142. event already on the map, it does NOT spawn this event. The script by default
  143. prints a warning to the console if this happens.
  144. =end
  145.  
  146. module BadMinotaur_Beacons
  147.  
  148.   #Change these to change what map your beacons spawn from.
  149.   #The map name is usually "MapXXX", where XXX is the map_id with leading
  150.   #zeroes if necessary. I left it open-ended in case any scripts change the
  151.   #naming convention of maps.
  152.   #If you're not sure what number and name the container map is, select the map
  153.   #in the map list and then look at the bottom right corner of your RPG Maker
  154.   #window. It should say something like "051: Map Name." The number (WITHOUT
  155.   #the leading zeroes) is the MAP_ID, and the MAP_NAME option is that number
  156.   #(WITH leading zeroes) after "Map" -- so our example map would have MAP_ID
  157.   #of 51 and a MAP_NAME of "Map051".
  158.   #Please note that MAP_NAME is not the name you give the map inside of RPG
  159.   #Maker! Instead, MAP_NAME is the *file name* of the map, without the .rvdata2
  160.   #extension on it.
  161.   MAP_ID = 2
  162.   MAP_NAME = "Map002"
  163.  
  164.   #A number, 1-100. Chance of a beacon spawning a rare beacontype.
  165.   BEACON_RARE_CHANCE = 25
  166.  
  167.   #Tell the script not to print warnings to console when a beacontype isn't
  168.   #supported. Change this to "true" and the script will shut up.
  169.   SHUT_UP_BEACONTYPE = false
  170.   #Tell the script not to print warnings to console when a unique NPC fails to
  171.   #spawn. Change this to "true" and the script will shut up.
  172.   SHUT_UP_UNIQUE = false
  173.  
  174. end
  175.  
  176. #We've spun this off into its own class to avoid making sweeping, terrible
  177. #changes to Game_Event for the sake of NPC spawning working correctly.
  178. class Game_BM_NPC < Game_Event
  179.  
  180.   #We're resetting the self-switches. This is to prevent any weird occurences cropping up when
  181.   #using generics.
  182.   def change_self_switches
  183.     keys = [[@map_id, @id, 'A'],
  184.              [@map_id, @id, 'B'],
  185.              [@map_id, @id, 'C'],
  186.              [@map_id, @id, 'D']]
  187.  
  188.     keys.each do |ks|
  189.       $game_self_switches[ks] = false
  190.     end
  191.   end
  192.  
  193.   #This is for beacons.
  194.   #Specifically, when we change the ID of the event, we need to change a few
  195.   #other things with it too.
  196.   #Returns the new id.
  197.   def id=(value)
  198.     @old_id = @id
  199.     @id = value
  200.     @event.id = value
  201.     change_self_switches
  202.     refresh
  203.     @id
  204.   end
  205.  
  206. end
  207.  
  208. class Game_Map
  209.  
  210.   alias :bmb_old_setup :setup
  211.  
  212.   #Changing the map setup method.
  213.   #We're adding the NPCSpawner method to build the NPC hash, full of possible
  214.   #beacons. Then, we'll set them up and, if any beacons were found, spawn them.
  215.   def setup(map_id)    
  216.     bmb_old_setup(map_id)
  217.     NPCSpawner.build_npc_hash
  218.     setup_beacons
  219.     spawn_npcs if @bmb_beacons.length > 0   #If it found any beacons, spawn them.
  220.   end
  221.  
  222.   #Set up the beacons for spawning.
  223.   #We look at each event, and check to see if it's a beacon. If so, we push it
  224.   #to the beacons array. It'll be spawned in Game_Map::setup by Game_Map::
  225.   #spawn_npcs.
  226.   def setup_beacons
  227.     @bmb_beacons = Array.new
  228.     @events.each_pair do |key, value|
  229.       if value.bmb_beacon?
  230.         @bmb_beacons.push(value) if value.bmb_beacon?
  231.         @events.delete(key) #Delete the beacon to reduce lag. It's useless now
  232.                             #anyway.
  233.       end
  234.     end
  235.   end
  236.  
  237.   def spawn_npcs
  238.     #For each beacon, look at its beacontype and then spawn something from it.
  239.     #If there's no matching beacontype, we warn the user via console, to help
  240.     #rooting out typos.
  241.     @bmb_beacons.each do |n|
  242.       if NPCSpawner.npc_hash[n.bmb_beacon]
  243.         NPCSpawner.spawn(NPCSpawner.get_random_npc_with_rare(n.bmb_beacon, n.bmb_rare), n.x, n.y)
  244.       else
  245.         unless BadMinotaur_Beacons::SHUT_UP_BEACONTYPE
  246.           puts "WARNING: Beacon \"" + n.bmb_beacon.to_s + "\" tried to spawn NPCs, but found no matching beacontypes!"
  247.           puts "ID: " + n.id.to_s + "    X: " + n.x.to_s + "  Y: " + n.y.to_s
  248.         end
  249.       end
  250.     end
  251.   end
  252.  
  253.   #We push each event into a queue, so that everything is spawned all nice and orderly.
  254.   #This ensures there are no missing IDs, that all events are accounted for.
  255.   def push_event_into_queue(event)
  256.     #This unsightly block of code is here to fill in missing gaps. If there is
  257.     #a gap in between, for example, event #10 and event #12, this will place
  258.     #the new event as event id #11.
  259.     highest_key = 0
  260.     newnum = 0
  261.     @events.each_key {|key| highest_key = key if highest_key < key}
  262.     highest_key.times do |n|
  263.       newnum = n if !@events[n]
  264.     end
  265.    
  266.     #Here is where we actually change the id.
  267.     newnum = @events.length+1 if newnum == 0
  268.     event.id = newnum
  269.     @events[newnum] = event
  270.     #We push the sprite, to force it to display.
  271.     SceneManager.scene.push_new_sprite(@events[newnum]) if SceneManager.scene_is?(Scene_Map)
  272.   end
  273.  
  274. end
  275.  
  276. module NPCSpawner
  277.  
  278.   #Variables for maps.
  279.   @map_name = BadMinotaur_Beacons::MAP_NAME
  280.   @map_id = BadMinotaur_Beacons::MAP_ID
  281.  
  282.   #Actually getting the events.
  283.   def self.get_summons
  284.     map = load_data("Data/" + @map_name + ".rvdata2")
  285.     map.events
  286.   end
  287.  
  288.   def self.npc_hash; @npc_hash; end;
  289.    
  290.   #We're going to call this at the beginning of every map loading, to get
  291.   #new pages and the suchlike done.
  292.   def self.build_npc_hash
  293.     #The NPC hash. This is blank at startup but is built during Game_Map.new.
  294.     @npc_hash = {}
  295.     #Convert the raw NPCs to Game_Events and then process the event.
  296.     event_hash = {}
  297.     get_summons.each_value do |event|
  298.       e = Game_BM_NPC.new($game_map.map_id, event.clone)
  299.       e.refresh
  300.       if e.bmb_beacontype
  301.         e.bmb_beacontype.split.each do |s|
  302.           s.strip!
  303.           if event_hash[s]
  304.             event_hash[s].push(e.clone)
  305.           else
  306.             event_hash[s] = Array.new
  307.             event_hash[s].push(e.clone)
  308.           end
  309.         end
  310.       end
  311.     end
  312.     @npc_hash = event_hash
  313.   end
  314.  
  315.   #This is all the stuff we need to actually spawn an NPC.
  316.   #This method clones the npc we're fed, moves it to where it needs to go, and
  317.   #pushes the new event into the game's map's queue.
  318.   def self.spawn(npc, x, y)
  319.     return nil if !npc
  320.     new_npc = npc.clone
  321.     new_npc.moveto(x, y)
  322.     $game_map.push_event_into_queue(new_npc)
  323.     new_npc
  324.   end
  325.  
  326.   #This is just a call to help out fellow scripters who want to use the code,
  327.   #and make it easier to use this functionality in the "Script Call..." event
  328.   #command.
  329.   def self.spawn_beacontype(beacontype, x, y)
  330.     spawn(get_random_npc(beacontype), x, y)
  331.   end
  332.  
  333.   #The same as above, but with rare spawning.
  334.   def self.spawn_beacontype_with_rare(beacontype, raretype, x, y)
  335.     spawn(get_random_npc_with_rare(beacontype, raretype), x, y)
  336.   end
  337.  
  338.   #Check for uniqueness.
  339.   #Returns false if the event shares the graphic name and graphic index with
  340.   #another event on the map. Returns true if it is truly unique.
  341.   def self.is_event_unique?(event)
  342.     return true if event.bmb_generic?
  343.     $game_map.events.each_value do |evt|
  344.       if event.character_name == evt.character_name && event.character_index == evt.character_index
  345.         puts "WARNING: Tried and failed to spawn a <unique> NPC with graphic name \"" + event.character_name +
  346.         "\" and with graphic index " + character_index.to_s + "!" unless BadMinotaur_Beacons::SHUT_UP_UNIQUE
  347.         return false
  348.       end
  349.     end
  350.     return true
  351.   end
  352.  
  353.   #Get a random NPC of a beacontype.
  354.   #It includes a warning, to help facilitate typo detections.
  355.   def self.get_random_npc(beacontype="citizen")
  356.     if @npc_hash[beacontype]
  357.       new_npc = @npc_hash[beacontype][Random.rand(@npc_hash[beacontype].length)]
  358.       if is_event_unique?(new_npc)
  359.         return new_npc
  360.       else
  361.         return nil
  362.       end
  363.     else
  364.       puts "WARNING: Tried to spawn unsupported beacontype \"" + beacontype.to_s + "\"!"
  365.       return nil
  366.     end
  367.   end
  368.  
  369.   #Get an NPC with rare support.
  370.   def self.get_random_npc_with_rare(beacontype="citizen", raretype=nil)
  371.     return get_random_npc(beacontype) unless raretype
  372.     r = Random.rand(100)
  373.     case r
  374.     when (BadMinotaur_Beacons::BEACON_RARE_CHANCE+1)..100
  375.       return get_random_npc(beacontype) if beacontype
  376.       return nil
  377.     when 0..BadMinotaur_Beacons::BEACON_RARE_CHANCE
  378.       return get_random_npc(raretype)
  379.     end
  380.   end
  381.  
  382. end
  383.  
  384. #Allow other classes to refresh the spriteset.
  385. class Scene_Map
  386.   def refresh_spriteset_characters
  387.     @spriteset.refresh_characters
  388.   end
  389.  
  390.   def push_new_sprite(event)
  391.     @spriteset.push_new_sprite(event)
  392.   end
  393.  
  394. end
  395.  
  396. class Spriteset_Map
  397.   def push_new_sprite(event)
  398.     @character_sprites.push(Sprite_Character.new(@viewport1, event))
  399.   end
  400. end
  401.  
  402. class Game_Event
  403.  
  404.   alias :bmb_old_initialize :initialize
  405.   attr_accessor :bmb_tagline
  406.   attr_accessor :id
  407.   attr_reader :bmb_beacon
  408.   attr_reader :bmb_beacontype
  409.   attr_reader :bmb_rare
  410.  
  411.   def initialize(map_id, event)
  412.     bmb_old_initialize(map_id, event)
  413.    
  414.     @bmb_tagline = bmb_check_for_tags
  415.     bmb_process_tags(@bmb_tagline)
  416.   end
  417.  
  418.   #Process event tags, putting the important ones in variables.
  419.   def bmb_process_tags(tagline)
  420.     @bmb_beacontype = bmb_check_for_def(/<beacontype:([^>]*)>/)
  421.     @bmb_beacon = bmb_check_for_def(/<beacon:\s?(\w*)\s?>/)
  422.     @bmb_unique = bmb_check_for_def(/<(unique)>/)
  423.     @bmb_rare = bmb_check_for_def(/<rare:\s?(\w*)\s?>/)
  424.   end
  425.  
  426.   def bmb_check_for_tags
  427.     s = ""
  428.     #We're finding the right page to pull stuff from.
  429.     if find_proper_page
  430.       #Iterate through the list
  431.       find_proper_page.list.each do |i|
  432.         #Here we add on to the string 's' if we find a comment.
  433.         s = s + i.parameters[0].to_s if i.code == 108
  434.       end
  435.     #This else is just in case there IS NO proper page.
  436.     else
  437.       #If the pages are empty, just take the first page and scan it for comments and those are our tags.
  438.       if @event.pages.empty?
  439.         @event.pages[0].list.each do |i|
  440.           s = s + i.parameters[0].to_s if i.code == 108
  441.         end
  442.       end
  443.     end
  444.     return s
  445.   end
  446.  
  447.   #Check for a specific tag.
  448.   def bmb_check_for_def(matchvalue, entry=1)
  449.     match =  @bmb_tagline.match(matchvalue)
  450.     return match[entry] if match
  451.   end
  452.  
  453.   #Expose the status of the event in regards to being a beacon, unique, or generic.
  454.   #All of these are used in the NPCSpawner module, to check a beacon's status.
  455.   def bmb_beacon?; !!@bmb_beacon; end;
  456.   def bmb_unique?; !!@bmb_unique; end;
  457.   def bmb_generic?; !bmb_unique?; end;
  458.  
  459. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement