From f35a51f9753b493a815b4c9c960677a403bd4a83 Mon Sep 17 00:00:00 2001 From: Maruno17 Date: Sat, 2 Oct 2021 22:57:54 +0100 Subject: [PATCH] Rewrote and refactored follower code, fixed follower glitchiness actoss connected maps, fixed follower glitchiness around bridges --- .../002_Save data/005_Game_SaveConversions.rb | 21 + .../003_Game processing/002_Scene_Map.rb | 1 + .../004_Game classes/006_Game_MapFactory.rb | 9 +- .../004_Game classes/007_Game_Character.rb | 3 - .../004_Game classes/009_Game_Player.rb | 18 +- .../011_Game_DependentEvents.rb | 563 ------------------ .../004_Game classes/011_Game_Follower.rb | 203 +++++++ .../012_Game_FollowerFactory.rb | 360 +++++++++++ .../005_Sprites/004_Sprite_Reflection.rb | 1 + .../005_Sprites/006_Spriteset_Global.rb | 8 +- .../New Tilemap/001_NewTilemap.rb | 4 +- Data/Scripts/012_Overworld/001_Overworld.rb | 4 + .../012_Overworld/004_Overworld_FieldMoves.rb | 10 +- Data/Scripts/013_Items/001_Item_Utilities.rb | 2 +- Data/Scripts/013_Items/002_Item_Effects.rb | 6 +- 15 files changed, 625 insertions(+), 588 deletions(-) delete mode 100644 Data/Scripts/004_Game classes/011_Game_DependentEvents.rb create mode 100644 Data/Scripts/004_Game classes/011_Game_Follower.rb create mode 100644 Data/Scripts/004_Game classes/012_Game_FollowerFactory.rb diff --git a/Data/Scripts/002_Save data/005_Game_SaveConversions.rb b/Data/Scripts/002_Save data/005_Game_SaveConversions.rb index 9eab23d98..b0649ea5d 100644 --- a/Data/Scripts/002_Save data/005_Game_SaveConversions.rb +++ b/Data/Scripts/002_Save data/005_Game_SaveConversions.rb @@ -99,3 +99,24 @@ SaveData.register_conversion(:v20_add_battled_counts) do end end end + +SaveData.register_conversion(:v20_follower_data) do + essentials_version 20 + display_title 'Updating follower data format' + to_value :global_metadata do |global| + # NOTE: dependentEvents is still defined in class PokemonGlobalMetadata just + # for the sake of this conversion. It will be removed in future. + if global.dependentEvents && global.dependentEvents.length > 0 + global.followers = [] + global.dependentEvents.each do |follower| + data = FollowerData.new(follower[0], follower[1], "reflection", + follower[2], follower[3], follower[4], + follower[5], follower[6], follower[7]) + data.name = follower[8] + data.common_event_id = follower[9] + global.followers.push(data) + end + end + global.dependentEvents = nil + end +end diff --git a/Data/Scripts/003_Game processing/002_Scene_Map.rb b/Data/Scripts/003_Game processing/002_Scene_Map.rb index ae81d2022..097f25e1a 100644 --- a/Data/Scripts/003_Game processing/002_Scene_Map.rb +++ b/Data/Scripts/003_Game processing/002_Scene_Map.rb @@ -83,6 +83,7 @@ class Scene_Map when 8 then $game_player.turn_up end $game_player.straighten + $PokemonTemp.followers.map_transfer_followers $game_map.update disposeSpritesets RPG::Cache.clear diff --git a/Data/Scripts/004_Game classes/006_Game_MapFactory.rb b/Data/Scripts/004_Game classes/006_Game_MapFactory.rb index fed413494..e590bb8cf 100644 --- a/Data/Scripts/004_Game classes/006_Game_MapFactory.rb +++ b/Data/Scripts/004_Game classes/006_Game_MapFactory.rb @@ -243,6 +243,7 @@ class PokemonMapFactory return false end + # Returns the coordinate change to go from this position to other position def getRelativePos(thisMapID, thisX, thisY, otherMapID, otherX, otherY) if thisMapID == otherMapID # Both events share the same map return [otherX - thisX, otherY - thisY] @@ -251,12 +252,12 @@ class PokemonMapFactory if conns[thisMapID] for conn in conns[thisMapID] if conn[0] == otherMapID - posX = thisX + conn[1] - conn[4] + otherX - posY = thisY + conn[2] - conn[5] + otherY + posX = conn[4] - conn[1] + otherX - thisX + posY = conn[5] - conn[2] + otherY - thisY return [posX, posY] elsif conn[3] == otherMapID - posX = thisX + conn[4] - conn[1] + otherX - posY = thisY + conn[5] - conn[2] + otherY + posX = conn[1] - conn[4] + otherX - thisX + posY = conn[2] - conn[5] + otherY - thisY return [posX, posY] end end diff --git a/Data/Scripts/004_Game classes/007_Game_Character.rb b/Data/Scripts/004_Game classes/007_Game_Character.rb index c50a004a8..ba66b528f 100644 --- a/Data/Scripts/004_Game classes/007_Game_Character.rb +++ b/Data/Scripts/004_Game classes/007_Game_Character.rb @@ -748,9 +748,6 @@ class Game_Character @jump_count = Game_Map::REAL_RES_X / jump_speed_real # Number of frames to jump one tile end @stop_count = 0 - if self.is_a?(Game_Player) - $PokemonTemp.dependentEvents.pbMoveDependentEvents - end triggerLeaveTile end diff --git a/Data/Scripts/004_Game classes/009_Game_Player.rb b/Data/Scripts/004_Game classes/009_Game_Player.rb index 4e5da12d7..82fa35883 100644 --- a/Data/Scripts/004_Game classes/009_Game_Player.rb +++ b/Data/Scripts/004_Game classes/009_Game_Player.rb @@ -27,8 +27,13 @@ class Game_Player < Game_Character return $game_map end - def pbHasDependentEvents? - return $PokemonGlobal.dependentEvents.length>0 + def screen_z(height = 0) + ret = super + return ret + 1 + end + + def has_follower? + return $PokemonGlobal.followers.length > 0 end def can_run? @@ -90,7 +95,6 @@ class Game_Player < Game_Character if !$PokemonTemp.encounterTriggered @x += x_offset @y += y_offset - $PokemonTemp.dependentEvents.pbMoveDependentEvents increase_steps end elsif !check_event_trigger_touch(dir) @@ -353,7 +357,11 @@ class Game_Player < Game_Character super update_screen_position(last_real_x, last_real_y) # Update dependent events - $PokemonTemp.dependentEvents.updateDependentEvents + if (!@moved_last_frame || @stopped_last_frame || + (@stopped_this_frame && $PokemonGlobal.sliding)) && (moving? || jumping?) + $PokemonTemp.followers.move_followers + end + $PokemonTemp.followers.update # Count down the time between allowed bump sounds @bump_se -= 1 if @bump_se && @bump_se>0 # Finish up dismounting from surfing @@ -461,7 +469,7 @@ class Game_Player < Game_Character return if moving? # Try triggering events upon walking into them/in front of them if @moved_this_frame - $PokemonTemp.dependentEvents.pbTurnDependentEvents + $PokemonTemp.followers.turn_followers result = pbCheckEventTriggerFromDistance([2]) # Event determinant is via touch of same position event result |= check_event_trigger_here([1,2]) diff --git a/Data/Scripts/004_Game classes/011_Game_DependentEvents.rb b/Data/Scripts/004_Game classes/011_Game_DependentEvents.rb deleted file mode 100644 index 078c000df..000000000 --- a/Data/Scripts/004_Game classes/011_Game_DependentEvents.rb +++ /dev/null @@ -1,563 +0,0 @@ -class PokemonTemp - attr_writer :dependentEvents - - def dependentEvents - @dependentEvents = DependentEvents.new if !@dependentEvents - return @dependentEvents - end -end - - - -def pbRemoveDependencies() - $PokemonTemp.dependentEvents.removeAllEvents() - pbDeregisterPartner() rescue nil -end - -def pbAddDependency(event) - $PokemonTemp.dependentEvents.addEvent(event) -end - -def pbRemoveDependency(event) - $PokemonTemp.dependentEvents.removeEvent(event) -end - -def pbAddDependency2(eventID, eventName, commonEvent) - $PokemonTemp.dependentEvents.addEvent($game_map.events[eventID],eventName,commonEvent) -end - -# Gets the Game_Character object associated with a dependent event. -def pbGetDependency(eventName) - return $PokemonTemp.dependentEvents.getEventByName(eventName) -end - -def pbRemoveDependency2(eventName) - $PokemonTemp.dependentEvents.removeEventByName(eventName) -end - - - -class PokemonGlobalMetadata - attr_writer :dependentEvents - - def dependentEvents - @dependentEvents = [] if !@dependentEvents - return @dependentEvents - end -end - - - -def pbTestPass(follower,x,y,_direction=nil) - return $MapFactory.isPassableStrict?(follower.map.map_id,x,y,follower) -end - -# Same map only -def moveThrough(follower,direction) - oldThrough=follower.through - follower.through=true - case direction - when 2 then follower.move_down - when 4 then follower.move_left - when 6 then follower.move_right - when 8 then follower.move_up - end - follower.through=oldThrough -end - -# Same map only -def moveFancy(follower,direction) - deltaX=(direction == 6 ? 1 : (direction == 4 ? -1 : 0)) - deltaY=(direction == 2 ? 1 : (direction == 8 ? -1 : 0)) - newX = follower.x + deltaX - newY = follower.y + deltaY - # Move if new position is the player's, or the new position is passable, - # or the current position is not passable - if ($game_player.x==newX && $game_player.y==newY) || - pbTestPass(follower,newX,newY,0) || - !pbTestPass(follower,follower.x,follower.y,0) - oldThrough=follower.through - follower.through=true - case direction - when 2 then follower.move_down - when 4 then follower.move_left - when 6 then follower.move_right - when 8 then follower.move_up - end - follower.through=oldThrough - end -end - -# Same map only -def jumpFancy(follower,direction,leader) - deltaX=(direction == 6 ? 2 : (direction == 4 ? -2 : 0)) - deltaY=(direction == 2 ? 2 : (direction == 8 ? -2 : 0)) - halfDeltaX=(direction == 6 ? 1 : (direction == 4 ? -1 : 0)) - halfDeltaY=(direction == 2 ? 1 : (direction == 8 ? -1 : 0)) - middle=pbTestPass(follower,follower.x+halfDeltaX,follower.y+halfDeltaY,0) - ending=pbTestPass(follower,follower.x+deltaX, follower.y+deltaY, 0) - if middle - moveFancy(follower,direction) - moveFancy(follower,direction) - elsif ending - if pbTestPass(follower,follower.x,follower.y,0) - if leader.jumping? - follower.jump_speed_real = leader.jump_speed_real * Graphics.frame_rate / 40.0 - else - follower.jump_speed_real = leader.move_speed_real * Graphics.frame_rate / 20.0 - end - follower.jump(deltaX,deltaY) - else - moveThrough(follower,direction) - moveThrough(follower,direction) - end - end -end - -def pbFancyMoveTo(follower,newX,newY,leader) - if follower.x-newX==-1 && follower.y==newY - moveFancy(follower,6) - elsif follower.x-newX==1 && follower.y==newY - moveFancy(follower,4) - elsif follower.y-newY==-1 && follower.x==newX - moveFancy(follower,2) - elsif follower.y-newY==1 && follower.x==newX - moveFancy(follower,8) - elsif follower.x-newX==-2 && follower.y==newY - jumpFancy(follower,6,leader) - elsif follower.x-newX==2 && follower.y==newY - jumpFancy(follower,4,leader) - elsif follower.y-newY==-2 && follower.x==newX - jumpFancy(follower,2,leader) - elsif follower.y-newY==2 && follower.x==newX - jumpFancy(follower,8,leader) - elsif follower.x!=newX || follower.y!=newY - follower.moveto(newX,newY) - end -end - - - -class DependentEvents - attr_reader :lastUpdate - - def createEvent(eventData) - rpgEvent = RPG::Event.new(eventData[3],eventData[4]) - rpgEvent.id = eventData[1] - if eventData[9] - # Must setup common event list here and now - commonEvent = Game_CommonEvent.new(eventData[9]) - rpgEvent.pages[0].list = commonEvent.list - end - newEvent = Game_Event.new(eventData[0],rpgEvent,$MapFactory.getMap(eventData[2])) - newEvent.character_name = eventData[6] - newEvent.character_hue = eventData[7] - case eventData[5] # direction - when 2 then newEvent.turn_down - when 4 then newEvent.turn_left - when 6 then newEvent.turn_right - when 8 then newEvent.turn_up - end - return newEvent - end - - def initialize - # Original map, Event ID, Current map, X, Y, Direction - events=$PokemonGlobal.dependentEvents - @realEvents=[] - @lastUpdate=-1 - for event in events - @realEvents.push(createEvent(event)) - end - end - - def pbEnsureEvent(event, newMapID) - events=$PokemonGlobal.dependentEvents - for i in 0...events.length - # Check original map ID and original event ID - if events[i][0]==event.map_id && events[i][1]==event.id - # Change current map ID - events[i][2]=newMapID - newEvent=createEvent(events[i]) - # Replace event - @realEvents[i]=newEvent - @lastUpdate+=1 - return i - end - end - return -1 - end - - def pbFollowEventAcrossMaps(leader,follower,instant=false,leaderIsTrueLeader=true) - d=leader.direction - areConnected=$MapFactory.areConnected?(leader.map.map_id,follower.map.map_id) - # Get the rear facing tile of leader - facingDirection=10-d - if !leaderIsTrueLeader && areConnected - relativePos=$MapFactory.getThisAndOtherEventRelativePos(leader,follower) - # Assumes leader and follower are both 1x1 tile in size - if (relativePos[1]==0 && relativePos[0]==2) # 2 spaces to the right of leader - facingDirection=6 - elsif (relativePos[1]==0 && relativePos[0]==-2) # 2 spaces to the left of leader - facingDirection=4 - elsif relativePos[1]==-2 && relativePos[0]==0 # 2 spaces above leader - facingDirection=8 - elsif relativePos[1]==2 && relativePos[0]==0 # 2 spaces below leader - facingDirection=2 - end - end - facings=[facingDirection] # Get facing from behind -# facings.push([0,0,4,0,8,0,2,0,6][d]) # Get right facing -# facings.push([0,0,6,0,2,0,8,0,4][d]) # Get left facing - if !leaderIsTrueLeader - facings.push(d) # Get forward facing - end - mapTile=nil - if areConnected - bestRelativePos=-1 - oldthrough=follower.through - follower.through=false - for i in 0...facings.length - facing=facings[i] - tile=$MapFactory.getFacingTile(facing,leader) - # Assumes leader is 1x1 tile in size - passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower) - if i==0 && !passable && tile && - $MapFactory.getTerrainTag(tile[0],tile[1],tile[2]).ledge - # If the tile isn't passable and the tile is a ledge, - # get tile from further behind - tile=$MapFactory.getFacingTileFromPos(tile[0],tile[1],tile[2],facing) - passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower) - end - if passable - relativePos=$MapFactory.getThisAndOtherPosRelativePos( - follower,tile[0],tile[1],tile[2]) - # Assumes follower is 1x1 tile in size - distance=Math.sqrt(relativePos[0]*relativePos[0]+relativePos[1]*relativePos[1]) - if bestRelativePos==-1 || bestRelativePos>distance - bestRelativePos=distance - mapTile=tile - end - if i==0 && distance<=1 # Prefer behind if tile can move up to 1 space - break - end - end - end - follower.through=oldthrough - else - tile=$MapFactory.getFacingTile(facings[0],leader) - # Assumes leader is 1x1 tile in size - passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower) - mapTile=passable ? mapTile : nil - end - if mapTile && follower.map.map_id==mapTile[0] - # Follower is on same map - newX=mapTile[1] - newY=mapTile[2] - deltaX=(d == 6 ? -1 : d == 4 ? 1 : 0) - deltaY=(d == 2 ? -1 : d == 8 ? 1 : 0) - posX = newX + deltaX - posY = newY + deltaY - follower.move_speed=leader.move_speed # sync movespeed - if (follower.x-newX==-1 && follower.y==newY) || - (follower.x-newX==1 && follower.y==newY) || - (follower.y-newY==-1 && follower.x==newX) || - (follower.y-newY==1 && follower.x==newX) - if instant - follower.moveto(newX,newY) - else - pbFancyMoveTo(follower,newX,newY,leader) - end - elsif (follower.x-newX==-2 && follower.y==newY) || - (follower.x-newX==2 && follower.y==newY) || - (follower.y-newY==-2 && follower.x==newX) || - (follower.y-newY==2 && follower.x==newX) - if instant - follower.moveto(newX,newY) - else - pbFancyMoveTo(follower,newX,newY,leader) - end - elsif follower.x!=posX || follower.y!=posY - if instant - follower.moveto(newX,newY) - else - pbFancyMoveTo(follower,posX,posY,leader) - pbFancyMoveTo(follower,newX,newY,leader) - end - end - else - if !mapTile - # Make current position into leader's position - mapTile=[leader.map.map_id,leader.x,leader.y] - end - if follower.map.map_id==mapTile[0] - # Follower is on same map as leader - follower.moveto(leader.x,leader.y) - else - # Follower will move to different map - events=$PokemonGlobal.dependentEvents - eventIndex=pbEnsureEvent(follower,mapTile[0]) - if eventIndex>=0 - newFollower=@realEvents[eventIndex] - newEventData=events[eventIndex] - newFollower.moveto(mapTile[1],mapTile[2]) - newEventData[3]=mapTile[1] - newEventData[4]=mapTile[2] - end - end - end - end - - def debugEcho - self.eachEvent { |e,d| - echoln d - echoln [e.map_id,e.map.map_id,e.id] - } - end - - def pbMapChangeMoveDependentEvents - events=$PokemonGlobal.dependentEvents - updateDependentEvents - leader=$game_player - for i in 0...events.length - event=@realEvents[i] - pbFollowEventAcrossMaps(leader,event,true,i==0) - # Update X and Y for this event - events[i][3]=event.x - events[i][4]=event.y - events[i][5]=event.direction - # Set leader to this event - leader=event - end - end - - def pbMoveDependentEvents - events=$PokemonGlobal.dependentEvents - updateDependentEvents - leader=$game_player - for i in 0...events.length - event=@realEvents[i] - pbFollowEventAcrossMaps(leader,event,false,i==0) - # Update X and Y for this event - events[i][3]=event.x - events[i][4]=event.y - events[i][5]=event.direction - # Set leader to this event - leader=event - end - end - - def pbTurnDependentEvents - events=$PokemonGlobal.dependentEvents - updateDependentEvents - leader=$game_player - for i in 0...events.length - event=@realEvents[i] - pbTurnTowardEvent(event,leader) - # Update direction for this event - events[i][5]=event.direction - # Set leader to this event - leader=event - end - end - - def eachEvent - events=$PokemonGlobal.dependentEvents - for i in 0...events.length - yield @realEvents[i],events[i] - end - end - - def updateDependentEvents - events=$PokemonGlobal.dependentEvents - return if events.length==0 - for i in 0...events.length - event=@realEvents[i] - next if !@realEvents[i] - event.transparent=$game_player.transparent - if event.jumping? || event.moving? || - !($game_player.jumping? || $game_player.moving?) - event.update - elsif !event.starting - event.set_starting - event.update - event.clear_starting - end - events[i][3]=event.x - events[i][4]=event.y - events[i][5]=event.direction - end - # Check event triggers - if Input.trigger?(Input::USE) && !$game_temp.in_menu && !$game_temp.in_battle && - !$game_player.move_route_forcing && !$game_temp.message_window_showing && - !pbMapInterpreterRunning? - # Get position of tile facing the player - facingTile=$MapFactory.getFacingTile() - # Assumes player is 1x1 tile in size - self.eachEvent { |e,d| - next if !d[9] - if e.at_coordinate?($game_player.x, $game_player.y) - # On same position - if !e.jumping? && (!e.respond_to?("over_trigger") || e.over_trigger?) - if e.list.size>1 - # Start event - $game_map.refresh if $game_map.need_refresh - e.lock - pbMapInterpreter.setup(e.list,e.id,e.map.map_id) - end - end - elsif facingTile && e.map.map_id==facingTile[0] && - e.at_coordinate?(facingTile[1], facingTile[2]) - # On facing tile - if !e.jumping? && (!e.respond_to?("over_trigger") || !e.over_trigger?) - if e.list.size>1 - # Start event - $game_map.refresh if $game_map.need_refresh - e.lock - pbMapInterpreter.setup(e.list,e.id,e.map.map_id) - end - end - end - } - end - end - - def removeEvent(event) - events=$PokemonGlobal.dependentEvents - mapid=$game_map.map_id - for i in 0...events.length - if events[i][2]==mapid && # Refer to current map - events[i][0]==event.map_id && # Event's map ID is original ID - events[i][1]==event.id - events[i]=nil - @realEvents[i]=nil - @lastUpdate+=1 - end - end - events.compact! - @realEvents.compact! - end - - def getEventByName(name) - events=$PokemonGlobal.dependentEvents - for i in 0...events.length - if events[i] && events[i][8]==name # Arbitrary name given to dependent event - return @realEvents[i] - end - end - return nil - end - - def removeAllEvents - events=$PokemonGlobal.dependentEvents - events.clear - @realEvents.clear - @lastUpdate+=1 - end - - def removeEventByName(name) - events=$PokemonGlobal.dependentEvents - for i in 0...events.length - if events[i] && events[i][8]==name # Arbitrary name given to dependent event - events[i]=nil - @realEvents[i]=nil - @lastUpdate+=1 - end - end - events.compact! - @realEvents.compact! - end - - def addEvent(event,eventName=nil,commonEvent=nil) - return if !event - events=$PokemonGlobal.dependentEvents - for i in 0...events.length - if events[i] && events[i][0]==$game_map.map_id && events[i][1]==event.id - # Already exists - return - end - end - # Original map ID, original event ID, current map ID, - # event X, event Y, event direction, - # event's filename, - # event's hue, event's name, common event ID - eventData=[ - $game_map.map_id,event.id,$game_map.map_id, - event.x,event.y,event.direction, - event.character_name.clone, - event.character_hue,eventName,commonEvent - ] - newEvent=createEvent(eventData) - events.push(eventData) - @realEvents.push(newEvent) - @lastUpdate+=1 - event.erase - end -end - - - -class DependentEventSprites - def initialize(viewport,map) - @disposed=false - @sprites=[] - @map=map - @viewport=viewport - refresh - @lastUpdate=nil - end - - def refresh - for sprite in @sprites - sprite.dispose - end - @sprites.clear - $PokemonTemp.dependentEvents.eachEvent { |event,data| - if data[0]==@map.map_id # Check original map - @map.events[data[1]].erase - end - if data[2]==@map.map_id # Check current map - @sprites.push(Sprite_Character.new(@viewport,event)) - end - } - end - - def update - if $PokemonTemp.dependentEvents.lastUpdate!=@lastUpdate - refresh - @lastUpdate=$PokemonTemp.dependentEvents.lastUpdate - end - for sprite in @sprites - sprite.update - end - end - - def dispose - return if @disposed - for sprite in @sprites - sprite.dispose - end - @sprites.clear - @disposed=true - end - - def disposed? - @disposed - end -end - - - -Events.onSpritesetCreate += proc { |_sender,e| - spriteset = e[0] # Spriteset being created - viewport = e[1] # Viewport used for tilemap and characters - map = spriteset.map # Map associated with the spriteset (not necessarily the current map) - spriteset.addUserSprite(DependentEventSprites.new(viewport,map)) -} - -Events.onMapSceneChange += proc { |_sender,e| - mapChanged = e[1] - if mapChanged - $PokemonTemp.dependentEvents.pbMapChangeMoveDependentEvents - end -} diff --git a/Data/Scripts/004_Game classes/011_Game_Follower.rb b/Data/Scripts/004_Game classes/011_Game_Follower.rb new file mode 100644 index 000000000..b7c039a58 --- /dev/null +++ b/Data/Scripts/004_Game classes/011_Game_Follower.rb @@ -0,0 +1,203 @@ +#=============================================================================== +# Instances of this are stored in @realEvents. +#=============================================================================== +class Game_Follower < Game_Event + attr_writer :map + + def initialize(event_data) + # Create RPG::Event to base self on + rpg_event = RPG::Event.new(event_data.x, event_data.y) + rpg_event.id = event_data.event_id + rpg_event.name = event_data.event_name + if event_data.common_event_id + # Must setup common event list here and now + common_event = Game_CommonEvent.new(event_data.common_event_id) + rpg_event.pages[0].list = common_event.list + end + # Create self + super(event_data.original_map_id, rpg_event, $MapFactory.getMap(event_data.current_map_id)) + # Modify self + self.character_name = event_data.character_name + self.character_hue = event_data.character_hue + case event_data.direction + when 2 then turn_down + when 4 then turn_left + when 6 then turn_right + when 8 then turn_up + end + end + + #============================================================================= + + def move_through(direction) + old_through = @through + @through = true + case direction + when 2 then move_down + when 4 then move_left + when 6 then move_right + when 8 then move_up + end + @through = old_through + end + + def move_fancy(direction) + delta_x = (direction == 6) ? 1 : (direction == 4) ? -1 : 0 + delta_y = (direction == 2) ? 1 : (direction == 8) ? -1 : 0 + new_x = self.x + delta_x + new_y = self.y + delta_y + # Move if new position is the player's, or the new position is passable, + # or self's current position is not passable + if ($game_player.x == new_x && $game_player.y == new_y) || + location_passable?(new_x, new_y, 10 - direction) || + !location_passable?(self.x, self.y, direction) + move_through(direction) + end + end + + def jump_fancy(direction, leader) + delta_x = (direction == 6) ? 2 : (direction == 4) ? -2 : 0 + delta_y = (direction == 2) ? 2 : (direction == 8) ? -2 : 0 + half_delta_x = delta_x / 2 + half_delta_y = delta_y / 2 + if location_passable?(self.x + half_delta_x, self.y + half_delta_y, 10 - direction) + # Can walk over the middle tile normally; just take two steps + move_fancy(direction) + move_fancy(direction) + elsif location_passable?(self.x + delta_x, self.y + delta_y, 10 - direction) + # Can't walk over the middle tile, but can walk over the end tile; jump over + if location_passable?(self.x, self.y, direction) + if leader.jumping? + @jump_speed_real = leader.jump_speed_real + else + # This is doubled because self has to jump 2 tiles in the time it + # takes the leader to move one tile. + @jump_speed_real = leader.move_speed_real * 2 + end + jump(delta_x, delta_y) + else + # self's current tile isn't passable; just take two steps ignoring passability + move_through(direction) + move_through(direction) + end + end + end + + def fancy_moveto(new_x, new_y, leader) + if self.x - new_x == 1 && self.y == new_y + move_fancy(4) + elsif self.x - new_x == -1 && self.y == new_y + move_fancy(6) + elsif self.x == new_x && self.y - new_y == 1 + move_fancy(8) + elsif self.x == new_x && self.y - new_y == -1 + move_fancy(2) + elsif self.x - new_x == 2 && self.y == new_y + jump_fancy(4, leader) + elsif self.x - new_x == -2 && self.y == new_y + jump_fancy(6, leader) + elsif self.x == new_x && self.y - new_y == 2 + jump_fancy(8, leader) + elsif self.x == new_x && self.y - new_y == -2 + jump_fancy(2, leader) + elsif self.x != new_x || self.y != new_y + moveto(new_x, new_y) + end + end + + #============================================================================= + + def turn_towards_leader(leader) + pbTurnTowardEvent(self, leader) + end + + def follow_leader(leader, instant = false, leaderIsTrueLeader = true) + maps_connected = $MapFactory.areConnected?(leader.map.map_id, self.map.map_id) + target = nil + # Get the target tile that self wants to move to + if maps_connected + behind_direction = 10 - leader.direction + target = $MapFactory.getFacingTile(behind_direction, leader) + if target && $MapFactory.getTerrainTag(target[0], target[1], target[2]).ledge + # Get the tile above the ledge (where the leader jumped from) + target = $MapFactory.getFacingTileFromPos(target[0], target[1], target[2], behind_direction) + end + target = [leader.map.map_id, leader.x, leader.y] if !target + else + # Map transfer to an unconnected map + target = [leader.map.map_id, leader.x, leader.y] + end + # Move self to the target + if self.map.map_id != target[0] + vector = $MapFactory.getRelativePos(target[0], 0, 0, self.map.map_id, @x, @y) + @map = $MapFactory.getMap(target[0]) + # NOTE: Can't use moveto because vector is outside the boundaries of the + # map, and moveto doesn't allow setting invalid coordinates. + @x = vector[0] + @y = vector[1] + @real_x = @x * Game_Map::REAL_RES_X + @real_y = @y * Game_Map::REAL_RES_Y + end + if instant || !maps_connected + moveto(target[1], target[2]) + else + fancy_moveto(target[1], target[2], leader) + end + end + + #============================================================================= + + def update_move + was_jumping = jumping? + super + if was_jumping && !jumping? + $scene.spriteset.addUserAnimation(Settings::DUST_ANIMATION_ID, self.x, self.y, true, 1) + end + end + + #============================================================================= + + private + + def location_passable?(x, y, direction) + this_map = self.map + return false if !this_map || !this_map.valid?(x,y) + return true if @through + passed_tile_checks = false + bit = (1 << (direction / 2 - 1)) & 0x0f + # Check all events for ones using tiles as graphics, and see if they're passable + for event in this_map.events.values + next if event.tile_id < 0 || event.through || !event.at_coordinate?(x, y) + tile_data = GameData::TerrainTag.try_get(this_map.terrain_tags[event.tile_id]) + next if tile_data.ignore_passability + next if tile_data.bridge && $PokemonGlobal.bridge == 0 + return false if tile_data.ledge + passage = this_map.passages[event.tile_id] || 0 + return false if passage & bit != 0 + passed_tile_checks = true if (tile_data.bridge && $PokemonGlobal.bridge > 0) || + (this_map.priorities[event.tile_id] || -1) == 0 + break if passed_tile_checks + end + # Check if tiles at (x, y) allow passage for followe + if !passed_tile_checks + for i in [2, 1, 0] + tile_id = this_map.data[x, y, i] || 0 + next if tile_id == 0 + tile_data = GameData::TerrainTag.try_get(this_map.terrain_tags[tile_id]) + next if tile_data.ignore_passability + next if tile_data.bridge && $PokemonGlobal.bridge == 0 + return false if tile_data.ledge + passage = this_map.passages[tile_id] || 0 + return false if passage & bit != 0 + break if tile_data.bridge && $PokemonGlobal.bridge > 0 + break if (this_map.priorities[tile_id] || -1) == 0 + end + end + # Check all events on the map to see if any are in the way + for event in this_map.events.values + next if !event.at_coordinate?(x, y) + return false if !event.through && event.character_name != "" + end + return true + end +end diff --git a/Data/Scripts/004_Game classes/012_Game_FollowerFactory.rb b/Data/Scripts/004_Game classes/012_Game_FollowerFactory.rb new file mode 100644 index 000000000..1d3e700ae --- /dev/null +++ b/Data/Scripts/004_Game classes/012_Game_FollowerFactory.rb @@ -0,0 +1,360 @@ +class FollowerData + attr_accessor :original_map_id + attr_accessor :event_id + attr_accessor :event_name + attr_accessor :current_map_id + attr_accessor :x, :y + attr_accessor :direction + attr_accessor :character_name, :character_hue + attr_accessor :name + attr_accessor :common_event_id + attr_accessor :visible + + def initialize(original_map_id, event_id, event_name, current_map_id, x, y, + direction, character_name, character_hue) + @original_map_id = original_map_id + @event_id = event_id + @event_name = event_name + @current_map_id = current_map_id + @x = x + @y = y + @direction = direction + @character_name = character_name + @character_hue = character_hue + @visible = true + end +end + +#=============================================================================== +# +#=============================================================================== +class Game_FollowerFactory + attr_reader :last_update + + def initialize + @events = [] + $PokemonGlobal.followers.each do |follower| + @events.push(create_follower_object(follower)) + end + @last_update = -1 + end + + #============================================================================= + + def add_follower(event, name = nil, common_event_id = nil) + return if !event + followers = $PokemonGlobal.followers + if followers.any? { |data| data.original_map_id == $game_map.map_id && data.event_id == event.id } + return # Event is already dependent + end + eventData = FollowerData.new($game_map.map_id, event.id, event.name, + $game_map.map_id, event.x, event.y, event.direction, + event.character_name.clone, event.character_hue) + eventData.name = name + eventData.common_event_id = common_event_id + newEvent = create_follower_object(eventData) + followers.push(eventData) + @events.push(newEvent) + @last_update += 1 + event.erase + end + + def remove_follower_by_event(event) + followers = $PokemonGlobal.followers + map_id = $game_map.map_id + followers.each_with_index do |follower, i| + next if follower.current_map_id != map_id + next if follower.original_map_id != event.map_id + next if follower.event_id != event.id + followers[i] = nil + @events[i] = nil + @last_update += 1 + end + followers.compact! + @events.compact! + end + + def remove_follower_by_name(name) + followers = $PokemonGlobal.followers + followers.each_with_index do |follower, i| + next if follower.name != name + followers[i] = nil + @events[i] = nil + @last_update += 1 + end + followers.compact! + @events.compact! + end + + def remove_all_followers + $PokemonGlobal.followers.clear + @events.clear + @last_update += 1 + end + + def get_follower_by_index(index = 0) + @events.each_with_index { |event, i| return event if i == index } + return nil + end + + def get_follower_by_name(name) + each_follower { |event, follower| return event if follower&.name == name } + return nil + end + + def each_follower + $PokemonGlobal.followers.each_with_index { |follower, i| yield @events[i], follower } + end + + #============================================================================= + + def turn_followers + leader = $game_player + $PokemonGlobal.followers.each_with_index do |follower, i| + event = @events[i] + event.turn_towards_leader(leader) + follower.direction = event.direction + leader = event + end + end + + def move_followers + leader = $game_player + $PokemonGlobal.followers.each_with_index do |follower, i| + event = @events[i] + event.follow_leader(leader, false, (i == 0)) + follower.x = event.x + follower.y = event.y + follower.current_map_id = event.map.map_id + follower.direction = event.direction + leader = event + end + end + + def map_transfer_followers + $PokemonGlobal.followers.each_with_index do |follower, i| + event = @events[i] + event.map = $game_map + event.moveto($game_player.x, $game_player.y) + event.direction = $game_player.direction + follower.x = event.x + follower.y = event.y + follower.current_map_id = event.map.map_id + follower.direction = event.direction + end + end + + #============================================================================= + + def update + followers = $PokemonGlobal.followers + return if followers.length == 0 + # Update all followers + leader = $game_player + followers.each_with_index do |follower, i| + event = @events[i] + next if !@events[i] + event.transparent = $game_player.transparent + event.move_speed = leader.move_speed + event.transparent = !follower.visible + if $PokemonGlobal.sliding + event.straighten + event.walk_anime = false + else + event.walk_anime = true + end + if event.jumping? || event.moving? || + !($game_player.jumping? || $game_player.moving?) + event.update + elsif !event.starting + event.set_starting + event.update + event.clear_starting + end + follower.direction = event.direction + leader = event + end + # Check event triggers + if Input.trigger?(Input::USE) && !$game_temp.in_menu && !$game_temp.in_battle && + !$game_player.move_route_forcing && !$game_temp.message_window_showing && + !pbMapInterpreterRunning? + # Get position of tile facing the player + facing_tile = $MapFactory.getFacingTile + # Assumes player is 1x1 tile in size + each_follower do |event, follower| + next if !follower.common_event_id + next if event.jumping? + if event.at_coordinate?($game_player.x, $game_player.y) + # On same position + if event.over_trigger? && event.list.size > 1 + # Start event + $game_map.refresh if $game_map.need_refresh + event.lock + pbMapInterpreter.setup(event.list, event.id, event.map.map_id) + end + elsif facing_tile && event.map.map_id == facing_tile[0] && + event.at_coordinate?(facing_tile[1], facing_tile[2]) + # On facing tile + if !event.over_trigger? && event.list.size > 1 + # Start event + $game_map.refresh if $game_map.need_refresh + event.lock + pbMapInterpreter.setup(event.list, event.id, event.map.map_id) + end + end + end + end + end + + #============================================================================= + + private + + def create_follower_object(event_data) + return Game_Follower.new(event_data) + end +end + +#=============================================================================== +# +#=============================================================================== +class FollowerSprites + def initialize(viewport) + @viewport = viewport + @sprites = [] + @last_update = nil + @disposed = false + end + + def dispose + return if @disposed + @sprites.each { |sprite| sprite.dispose } + @sprites.clear + @disposed = true + end + + def disposed? + return @disposed + end + + def refresh + @sprites.each { |sprite| sprite.dispose } + @sprites.clear + $PokemonTemp.followers.each_follower do |event, follower| + $MapFactory.maps.each do |map| + map.events[follower.event_id].erase if follower.original_map_id == map.map_id + end + @sprites.push(Sprite_Character.new(@viewport, event)) + end + end + + def update + if $PokemonTemp.followers.last_update != @last_update + refresh + @last_update = $PokemonTemp.followers.last_update + end + @sprites.each { |sprite| sprite.update } + end +end + +#=============================================================================== +# Stores Game_Follower instances just for the current play session. +#=============================================================================== +class PokemonTemp + attr_writer :followers + + def followers + @followers = Game_FollowerFactory.new if !@followers + return @followers + end +end + +#=============================================================================== +# Permanently stores data of dependent events (i.e. in save files). +#=============================================================================== +class PokemonGlobalMetadata + attr_accessor :dependentEvents # Deprecated + attr_writer :followers + + def followers + @followers = [] if !@followers + return @followers + end +end + +#=============================================================================== +# Helper module for adding/removing/getting followers. +#=============================================================================== +module Followers + module_function + + # @param event_id [Integer] ID of the event on the current map to be added as a follower + # @param name [String] identifier name of the follower to be added + # @param common_event_id [Integer] ID of the Common Event triggered when interacting with this follower + def add(event_id, name, common_event_id) + $PokemonTemp.followers.add_follower($game_map.events[event_id], name, common_event_id) + end + + # @param event [Game_Event] map event to be added as a follower + def add_event(event) + $PokemonTemp.followers.add_follower(event) + end + + # @param name [String] identifier name of the follower to be removed + def remove(name) + $PokemonTemp.followers.remove_follower_by_name(name) + end + + # @param event [Game_Event] map event to be removed as a follower + def remove_event(event) + $PokemonTemp.followers.remove_follower_by_event(event) + end + + # Removes all followers. + def clear + $PokemonTemp.followers.remove_all_followers + pbDeregisterPartner rescue nil + end + + # @param name [String, nil] name of the follower to get, or nil for the first follower + # @return [Game_Follower, nil] follower object + def get(name = nil) + return $PokemonTemp.followers.get_follower_by_name(name) if name + $PokemonTemp.followers.get_follower_by_index + return nil + end +end + + +#=============================================================================== +# Deprecated methods +#=============================================================================== +def pbAddDependency2(event_id, name, common_event_id) + Deprecation.warn_method('pbAddDependency2', 'v21', 'Followers.add(event_id, name, common_event_id)') + Followers.add_event(event) +end + +def pbAddDependency(event) + Deprecation.warn_method('pbAddDependency', 'v21', 'Followers.add_event(event)') + Followers.add_event(event) +end + +def pbRemoveDependency2(name) + Deprecation.warn_method('pbRemoveDependency2', 'v21', 'Followers.remove(name)') + Followers.remove(name) +end + +def pbRemoveDependency(event) + Deprecation.warn_method('pbRemoveDependency', 'v21', 'Followers.remove_event(event)') + Followers.remove_event(event) +end + +def pbRemoveDependencies + Deprecation.warn_method('pbRemoveDependencies', 'v21', 'Followers.clear') + Followers.clear +end + +def pbGetDependency(name) + Deprecation.warn_method('pbGetDependency', 'v21', 'Followers.get(name)') + Followers.get(name) +end diff --git a/Data/Scripts/005_Sprites/004_Sprite_Reflection.rb b/Data/Scripts/005_Sprites/004_Sprite_Reflection.rb index 14dc72b9a..dda104aa8 100644 --- a/Data/Scripts/005_Sprites/004_Sprite_Reflection.rb +++ b/Data/Scripts/005_Sprites/004_Sprite_Reflection.rb @@ -63,6 +63,7 @@ class Sprite_Reflection @sprite.oy = height/2-2 # Hard-coded 2 pixel shift up @sprite.oy -= @rsprite.character.bob_height*2 @sprite.z = -50 # Still water is -100, map is 0 and above + @sprite.z += 1 if @event == $game_player @sprite.zoom_x = @rsprite.zoom_x @sprite.zoom_y = @rsprite.zoom_y frame = (Graphics.frame_count%40)/10 diff --git a/Data/Scripts/005_Sprites/006_Spriteset_Global.rb b/Data/Scripts/005_Sprites/006_Spriteset_Global.rb index 2b28db7a6..9837a6219 100644 --- a/Data/Scripts/005_Sprites/006_Spriteset_Global.rb +++ b/Data/Scripts/005_Sprites/006_Spriteset_Global.rb @@ -4,6 +4,7 @@ class Spriteset_Global @@viewport2.z = 200 def initialize + @follower_sprites = FollowerSprites.new(Spriteset_Map.viewport) @playersprite = Sprite_Character.new(Spriteset_Map.viewport, $game_player) @picture_sprites = [] for i in 1..100 @@ -14,15 +15,18 @@ class Spriteset_Global end def dispose + @follower_sprites.dispose + @follower_sprites = nil @playersprite.dispose - @picture_sprites.each { |sprite| sprite.dispose } - @timer_sprite.dispose @playersprite = nil + @picture_sprites.each { |sprite| sprite.dispose } @picture_sprites.clear + @timer_sprite.dispose @timer_sprite = nil end def update + @follower_sprites.update @playersprite.update @picture_sprites.each { |sprite| sprite.update } @timer_sprite.update diff --git a/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb b/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb index 492ab4511..727630dee 100644 --- a/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb +++ b/Data/Scripts/006_Map renderer/New Tilemap/001_NewTilemap.rb @@ -510,8 +510,8 @@ class TilemapRenderer if MapFactoryHelper.hasConnections?(@current_map_id) offsets = $MapFactory.getRelativePos(@current_map_id, 0, 0, $game_map.map_id, 0, 0) if offsets - @tile_offset_x += offsets[0] - @tile_offset_y += offsets[1] + @tile_offset_x -= offsets[0] + @tile_offset_y -= offsets[1] else ret = true # Need a full refresh end diff --git a/Data/Scripts/012_Overworld/001_Overworld.rb b/Data/Scripts/012_Overworld/001_Overworld.rb index 09e6e53db..d0892eaff 100644 --- a/Data/Scripts/012_Overworld/001_Overworld.rb +++ b/Data/Scripts/012_Overworld/001_Overworld.rb @@ -548,20 +548,24 @@ end def pbSlideOnIce return if !$game_player.pbTerrainTag.ice + $PokemonTemp.followers.update $PokemonGlobal.sliding = true direction = $game_player.direction oldwalkanime = $game_player.walk_anime $game_player.straighten $game_player.walk_anime = false + first_loop = true loop do break if !$game_player.can_move_in_direction?(direction) break if !$game_player.pbTerrainTag.ice $game_player.move_forward + $PokemonTemp.followers.move_followers if first_loop while $game_player.moving? pbUpdateSceneMap Graphics.update Input.update end + first_loop = false end $game_player.center($game_player.x, $game_player.y) $game_player.straighten diff --git a/Data/Scripts/012_Overworld/004_Overworld_FieldMoves.rb b/Data/Scripts/012_Overworld/004_Overworld_FieldMoves.rb index 5ad3bb126..5108cfdbb 100644 --- a/Data/Scripts/012_Overworld/004_Overworld_FieldMoves.rb +++ b/Data/Scripts/012_Overworld/004_Overworld_FieldMoves.rb @@ -255,7 +255,7 @@ HiddenMoveHandlers::CanUseMove.add(:DIG,proc { |move,pkmn,showmsg| pbMessage(_INTL("Can't use that here.")) if showmsg next false end - if $game_player.pbHasDependentEvents? + if $game_player.has_follower? pbMessage(_INTL("It can't be used when you have someone with you.")) if showmsg next false end @@ -494,7 +494,7 @@ HiddenMoveHandlers::UseMove.add(:FLASH,proc { |move,pokemon| #=============================================================================== HiddenMoveHandlers::CanUseMove.add(:FLY,proc { |move,pkmn,showmsg| next false if !pbCheckHiddenMoveBadge(Settings::BADGE_FOR_FLY,showmsg) - if $game_player.pbHasDependentEvents? + if $game_player.has_follower? pbMessage(_INTL("It can't be used when you have someone with you.")) if showmsg next false end @@ -696,7 +696,7 @@ HiddenMoveHandlers::UseMove.add(:STRENGTH,proc { |move,pokemon| #=============================================================================== def pbSurf return false if $game_player.pbFacingEvent - return false if $game_player.pbHasDependentEvents? + return false if $game_player.has_follower? move = :SURF movefinder = $Trainer.get_pokemon_with_move(move) if !pbCheckHiddenMoveBadge(Settings::BADGE_FOR_SURF,false) || (!$DEBUG && !movefinder) @@ -770,7 +770,7 @@ HiddenMoveHandlers::CanUseMove.add(:SURF,proc { |move,pkmn,showmsg| pbMessage(_INTL("You're already surfing.")) if showmsg next false end - if $game_player.pbHasDependentEvents? + if $game_player.has_follower? pbMessage(_INTL("It can't be used when you have someone with you.")) if showmsg next false end @@ -865,7 +865,7 @@ HiddenMoveHandlers::CanUseMove.add(:TELEPORT,proc { |move,pkmn,showmsg| pbMessage(_INTL("Can't use that here.")) if showmsg next false end - if $game_player.pbHasDependentEvents? + if $game_player.has_follower? pbMessage(_INTL("It can't be used when you have someone with you.")) if showmsg next false end diff --git a/Data/Scripts/013_Items/001_Item_Utilities.rb b/Data/Scripts/013_Items/001_Item_Utilities.rb index e59bab48d..7ccd32c68 100644 --- a/Data/Scripts/013_Items/001_Item_Utilities.rb +++ b/Data/Scripts/013_Items/001_Item_Utilities.rb @@ -454,7 +454,7 @@ def pbBikeCheck pbMessage(_INTL("Can't use that here.")) return false end - if $game_player.pbHasDependentEvents? + if $game_player.has_follower? pbMessage(_INTL("It can't be used when you have someone with you.")) return false end diff --git a/Data/Scripts/013_Items/002_Item_Effects.rb b/Data/Scripts/013_Items/002_Item_Effects.rb index 67fa47a04..5a16a9bf8 100644 --- a/Data/Scripts/013_Items/002_Item_Effects.rb +++ b/Data/Scripts/013_Items/002_Item_Effects.rb @@ -21,7 +21,7 @@ ItemHandlers::UseFromBag.add(:HONEY,proc { |item| }) ItemHandlers::UseFromBag.add(:ESCAPEROPE,proc { |item| - if $game_player.pbHasDependentEvents? + if $game_player.has_follower? pbMessage(_INTL("It can't be used when you have someone with you.")) next 0 end @@ -66,7 +66,7 @@ ItemHandlers::ConfirmUseInField.add(:ESCAPEROPE,proc { |item| pbMessage(_INTL("Can't use that here.")) next false end - if $game_player.pbHasDependentEvents? + if $game_player.has_follower? pbMessage(_INTL("It can't be used when you have someone with you.")) next false end @@ -158,7 +158,7 @@ ItemHandlers::UseInField.add(:ESCAPEROPE,proc { |item| pbMessage(_INTL("Can't use that here.")) next false end - if $game_player.pbHasDependentEvents? + if $game_player.has_follower? pbMessage(_INTL("It can't be used when you have someone with you.")) next false end