Notes to Self: Little tweaks, mostly chrono engine

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
Every now and then I'm using a plugin and it doesn't work quite the way I want it to, so I put in a little tweak to do what I want. Sometimes it's a bugfix, sometimes it's just changing things to be easier for me to work with. Since I'm not the only person using these plugins, I figured I'd put up a thread for these so other people can try them if they want.

I'll be sporadically updating this whenever I have something I think other people might find useful.

These are not optimized or cleaned up in any way, so it's almost certain there are better ways to do all of these things.

Terms of Use: Do whatever you like.

YEP_X_ExtDoodadPack1: Switching Doodads 'on' puts them at the beginning of their animation cycle.
Yanfly's extended doodad plugin with switches is great to use, but when you switch an animated doodad on, it's at essentially random part of its animation cycle. That's because the doodads are silently going through each frame while transparent.

I wanted animated doodads to be at the start of their animation cycle when switched on. This tweak does that by resetting the frame to 0 whenever the plugin refreshes a hidden doodad.
Code:
//from YEP_X_ExtDoodadPack1
//
Sprite_Doodad.prototype.updateCustomEDP1Z = function() {
  if ($gameTemp._modeGFD) return;
  // Party
  var length = this.partyHave.length;
  for (var i = 0; i < length; ++i) {
    var actorId = this.partyHave[i];
    if (!$gameParty._actors.contains(actorId)) {
      this.opacity = 0;
     this._index = 0;//reset the animation state so long as opacity is off, thus allowing for us to play doodad animations once.
      return;
    }
  }
  var length = this.partyMiss.length;
  for (var i = 0; i < length; ++i) {
    var actorId = this.partyMiss[i];
    if ($gameParty._actors.contains(actorId)) {
      this.opacity = 0;
     this._index = 0;//reset the animation state so long as opacity is off, thus allowing for us to play doodad animations once.
      return;
    }
  }
  // Switches
  var length = this.switchOn.length;
  for (var i = 0; i < length; ++i) {
    var switchId = this.switchOn[i];
    if (!$gameSwitches.value(switchId)) {
      this.opacity = 0;
     this._index = 0;//reset the animation state so long as opacity is off, thus allowing for us to play doodad animations once.
      return;
    }
  }
  var length = this.switchOff.length;
  for (var i = 0; i < length; ++i) {
    var switchId = this.switchOff[i];
    if ($gameSwitches.value(switchId)) {
      this.opacity = 0;
     this._index = 0;//reset the animation state so long as opacity is off, thus allowing for us to play doodad animations once.
      return;
    }
  }
};


MOG_ChronoEngine Item Core Compatibilty
Chrono Engine is not compatible with yanfly's item core by default. This fixes that.
Code:
var _mog_toolSys_gmap_setup = Game_Map.prototype.setup;
Game_Map.prototype.setup = function(mapId) {
   //console.log("Game_Map.prototype.setup");
   _mog_toolSys_gmap_setup.call(this,mapId);
   this._treasureEvents = [];
   this._battlersOnScreen = [];
   this._enemiesOnScreen = [];
   this._actorsOnScreen = [];
   $gameSystem._toolsOnMap = [];
   $gameTemp.clearToolCursor();
   //console.log($gameSystem._toolsData);// with itemcore, this always returns an empty array []
   //without itemcore, it returns 'null', then an the proper item  list, so clearly _toolsdata isn't getting set right
   //I assume the 'null' gets overridden by one of yanfly's blanket definitions, and since I can't find that
   // I'm just testing to see if the list is empty.  If it is, grab the tools again
   // it seems to work!
   //if (!$gameSystem._toolsData) {this.dataMapToolClear()};
   if (!$gameSystem._toolsData) {this.dataMapToolClear()
       }else{ if ($gameSystem._toolsData.length == 0) {this.dataMapToolClear()};
       }
   for (var i = 0; i < $gameParty.members().length; i++) {
       var actor = $gameParty.members()[i];
       actor.clearActing();
       $gameSystem._toolHookshotSprite = [null,null,0];
   };
};

MOG_ActorHud Meter Angle Fix
Mog's actorhud converts from radians to degrees twice by default, which means if you set an angled bar the background
won't be at the same angle as the filled bar. This typo is in all the different meters, but once you see the hp meter fix you can change whatever other ones you want.
Code:
//==============================
// * Create HP Meter
//==============================
Actor_Hud.prototype.create_hp_meter = function() {
   if (String(Moghunter.ahud_hp_meter_visible) != "true") {return};
   this.removeChild(this._hp_meter_blue);
   this.removeChild(this._hp_meter_red);
   if (!this._battler) {return};
   this._hp_meter_red = new Sprite(this._hp_meter_img);
   this._hp_meter_red.x = this._pos_x + Moghunter.ahud_hp_meter_pos_x;
   this._hp_meter_red.y = this._pos_y + Moghunter.ahud_hp_meter_pos_y;
   this._hp_meter_red.rotation = Moghunter.ahud_hp_meter_rotation * Math.PI / 180;
   this._hp_meter_red.setFrame(0,0,0,0);
   this.addChild(this._hp_meter_red);    
   this._hp_meter_blue = new Sprite(this._hp_meter_img);
   this._hp_meter_blue.x = this._hp_meter_red.x;
   this._hp_meter_blue.y = this._hp_meter_red.y;
   this._hp_meter_blue.rotation = this._hp_meter_red.rotation; //this was incorrectly converting to radians twice
   this._hp_meter_blue.setFrame(0,0,0,0);
   this.addChild(this._hp_meter_blue);
   this._hp_old_ani[0] = this._battler.hp - 1;
   if (String(Moghunter.ahud_hp_meter_flow) === "true") {this._hp_flow[0] = true;
       this._hp_flow[2] = this._hp_meter_img.width / 3;
       this._hp_flow[3] = this._hp_flow[2] * 2;
       this._hp_flow[1] = Math.floor(Math.random() * this._hp_flow[2]);
   };
};


MOG_CharacterPoses+Mog_ChronoEngine Rotation Around Center + Set Sprite Rotations
By default in rpgmaker, sprite rotation is around the base of a sprite. That's bad if you're rotating projectiles, since you end up with your hitbox being displaced off from where the sprite actually is.

This function uses trig to identify where the sprite needs to be displaced so that it's rotated around the hitbox. (This requires that you center your sprite using mog's displacement tools to start with).


Above: My Version (approximate hitbox in blue)
Below: Default version

In addition, I have added a few functions for rotating character sprites smoothly. Set a target angle and a rotation rate, and it will update each frame until it reaches the target angle.

If you don't set a target angle, it will rotate at that speed forever.

You can also set it to go in whichever direction is closer, ignoring the sign on the angle speed.

Code:
Game_CharacterBase.prototype.setTargetAngle = function(targetAngle) {
   var ag = targetAngle * Math.PI / 180;
    this._user.targetAngle = [ag,targetAngle];
};

Game_CharacterBase.prototype.setRotationRate = function(rotationSpeed) {
   var ag = rotationSpeed * Math.PI / 180;
    this._user.rotationSpeed = [ag,rotationSpeed];
};

Game_CharacterBase.prototype.setRotationFlexible = function() {
   //flag to say we can go clockwise OR counterclockwise, whichever is closer
    this._user.rotationFlexible= true;
};

Game_CharacterBase.prototype.clearRotationRate = function() {
   this._user.targetAngle =null;
    this._user.rotationSpeed = null;
   this._user.rotationFlexible= false;

};



Sprite_Character.prototype.updateCharPosesPosition = function() {
	//honestly I don't understand what this is supposed to be doing, and a
	//constant offset for the x displacement fits what I want to do,
	//I've removed the x-offset shifting, to make way for my stuff
	//sorry if this breaks stuff for you!
	
	//if (this._character.direction() === 4 || this._character.direction() === 6) {	
	//	var ex = this._character.direction() === 6 ? this._character._frames.x : -this._character._frames.x;
    //} else {
	//	var ex = 0;
	//};


	var ex = this._character._frames.x;
	var ey = this._character._frames.y;	
	
	// add on additional rotation specified in tool
	if (this._character._tool)
	{
		if(this._character._tool.offsetX)
		{
			ex+=this._character._tool.offsetX;
		}
		if(this._character._tool.offsetY)
		{
			ey+=this._character._tool.offsetY;
		}
		if(this._character._tool.diagonalAngle)
		{
			this._character._user.rotation = [this._character._tool.diagonalAngle,Math.degrees(this._character._tool.diagonalAngle)];
		}
	}
	

	ag=0;
	angle=0;

	thisHasRotationUser=false;
	thisHasRotationChar=false;
	//technically this isn't the 'correct' way of checking to see
	//if we have this trait, but there shouldn't be any edge cases that matter
	//since if rotation has a value but it resolves as 'false' we don't want to
	//rotate after all
	if (this._character._user.rotation)
	{
		thisHasRotationChar=true;
		[ag,angle] = this._character._user.rotation;
	}else{
		if ($gameMap.event(3)._user.rotation[0] =undefined)
		{
			$gameMap.event(3)._user.rotation[0]=0;
			$gameMap.event(3)._user.rotation[1]=0;
		}
		
	}
	


	if (thisHasRotationChar)
	{
		var isSpinning=false;
		var hasTargetAngle=false;
		if(thisHasRotationChar)
		{
			if (this._character._user.rotationSpeed)
			{
				[agSpeed,angleSpeed] = this._character._user.rotationSpeed;
				isSpinning=true;
			}
			if (this._character._user.targetAngle || this._character._user.targetAngle ==0	)
			{
				[agTarget,angleTarget] = this._character._user.targetAngle;
				hasTargetAngle=true;
				
			}
			if (isSpinning){
				if(hasTargetAngle){
					//if our target angle is within a single step, snap to it.
					if(Math.abs(((((angleTarget+360) % 360)-((angle+360) % 360))/angleSpeed))<1){
						this._character.setAngle(angleTarget);
					}else{
						//if we are allowed to go in either direction, pick the shorter distance
						if(this._character._user.rotationFlexible)
						{
							if (((((angleTarget+360) % 360)-((angle+360) % 360) +360) % 360)>180)
								this._character.setAngle(angle-Math.abs(angleSpeed));
							else
								this._character.setAngle(angle+Math.abs(angleSpeed));
						}else{
							this._character.setAngle(angle+angleSpeed);
						}
					}
				}else{
					//if we don't have a target angle, but do have a rotation speed, keep spinning without limit!
					this._character.setAngle(angle+angleSpeed);
				}
			}
		}else if(thisHasRotationUser){
			if (this._user.rotationSpeed) 
			{
				[agSpeed,angleSpeed] = this._user.rotationSpeed;
				isSpinning=true;
			} 
			if (this._user.targetAngle) 
			{
				[agTarget,angleTarget] = this._user.targetAngle;
				hasTargetAngle=true;
			}
			if (isSpinning){
				if(hasTargetAngle){
					//if our target angle is within a single step, snap to it.
					if(Math.abs(((((angleTarget+360) % 360)-((angle+360) % 360))/angleSpeed))<1){
						this.setAngle(angleTarget);
					}else{
						if(this._user.rotationFlexible)
						{
							if (((((angleTarget+360) % 360)-((angle+360) % 360) +360) % 360)>180)
								this.setAngle(angle-Math.abs(angleSpeed));
							else
								this.setAngle(angle+Math.abs(angleSpeed));
						}else{
							this.setAngle(angle+angleSpeed);
						}
					}
				}else{
					//if we don't have a target angle, but do have a rotation speed, keep spinning without limit!
					this.setAngle(angle+angleSpeed);
				}
			}
		}

		
		//update ag and angle
		if (this._character._user.rotation || (this._character._user.rotation==0))
		{
			[ag,angle] = this._character._user.rotation;
		}
	}
	if (ag==0)
	{
	this.x += ex;
	this.y += ey;
	
	}else{
	//I have altered this code to
	//rotate events around the displacement point by default, 
	//instead of the base of the sprite	
	//technically this rotation is only off by a pixel or two, but w/e, close enough.
	baselength=Math.sqrt(ex*ex + ey*ey);
	

	this.x += ex*Math.cos(ag) - ey * Math.sin(ag);
	this.y += ex*Math.sin(ag) + ey * Math.cos(ag);

	}
	
};

//one problem with the above is that animations end up vertically displaced
//from their targets!  And animations are attached to a single point on the 
//sprite, which means if it spins, they spin!
//this restores normal animation location on rotating objects
_cross_sprite_animation_update_position=Sprite_Animation.prototype.updatePosition;
Sprite_Animation.prototype.updatePosition = function()
{
		_cross_sprite_animation_update_position.call(this);
		if (this._target._character)
		{
			//if we're targeting a sprite on a character
			//then
			var ex =0;
			var ey =0;
			
			if (this._target._character._frames.x)
			{
				ex+=this._target._character._frames.x;
			}
			if (this._target._character._frames.y)
			{
				ey+=this._target._character._frames.y;
			}
			if (this._target._character._tool)
			{
				if(this._target._character._tool.offsetX)
				{
					ex+=this._target._character._tool.offsetX;
				}
				if(this._target._character._tool.offsetY)
				{
					ey+=this._target._character._tool.offsetY;
				}
			}
			var ag=0;
			var angle=0;
			if (this._target._character._user.rotation || (this._target._character._user.rotation==0))
			{
				[ag,angle] = this._target._character._user.rotation;
			}
			
			this.x -= ex*Math.cos(ag) - ey * Math.sin(ag) -ex;
			this.y -= ex*Math.sin(ag) + ey * Math.cos(ag) -ey*1.5;
		}
		
}
 
Last edited:

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
Here's a JSX file I made for use as a photoshop macro. This takes in a single image that points to the right, and rotates it appropriately to create a 1-frame-wide sheet. I use it mostly for bullets.

If you combine with irfanview's Options->Extract Image tiles to slice up a spritesheet, and Image->create Panorama Image to reassemble, you can fairly quickly go through an animated sheet.

Code:
doc = app.activeDocument;  
//var creatureName = prompt("What is the file called?", "", "Enter filename");

    // strip the extension off
var fileNameNoExtension = doc.name;
fileNameNoExtension = fileNameNoExtension.split( "." );
if ( fileNameNoExtension.length > 1 ) {
   fileNameNoExtension.length--;
}
fileNameNoExtension = fileNameNoExtension.join(".");
creatureName=fileNameNoExtension;

var originalImage = app.activeDocument.activeLayer;

var savedRuler= app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var w = app.activeDocument.width;
var h = app.activeDocument.height;
doc.resizeImage(UnitValue(w*2,"px"),UnitValue(h*2,"px"),null,ResampleMethod.NEARESTNEIGHBOR);
w = app.activeDocument.width;
h = app.activeDocument.height;
//add a fringe of 1 pixel on each side to avoid bleeding
if(w>h) app.activeDocument.resizeCanvas (w+2, w+2, AnchorPosition.MIDDLECENTER);
if(w<h) app.activeDocument.resizeCanvas (h+2, h+2, AnchorPosition.MIDDLECENTER);
w = app.activeDocument.width;
h = app.activeDocument.height;
app.activeDocument.resizeCanvas (w, h*4, AnchorPosition.TOPCENTER);

var downView = originalImage.duplicate();
downView.rotate(90,AnchorPosition.MIDDLECENTER)
if(downView.bounds[0]%2==1) downView.translate(1,0)
if(downView.bounds[1]%2==1) downView.translate(0,-1)
if(downView.bounds[1]<0) downView.translate(0,2)

originalImage.translate(0,h)

var leftView = originalImage.duplicate();
originalImage.resize(-100,100)
leftView.translate(0,h)
if(leftView.bounds[0]%2==1) leftView.translate(-1,0)
if(leftView.bounds[1]%2==1) leftView.translate(0,1)


var upView = originalImage.duplicate();
upView.translate(0,2*h)
upView.resize(-100,100)
upView.rotate(-90,AnchorPosition.MIDDLECENTER)
upView.translate(1,-1)
if(upView.bounds[0]%2==1) upView.translate(-1,0)
if(upView.bounds[1]%2==1) upView.translate(0,1)
   
if(originalImage.bounds[0]%2==1) originalImage.translate(1,0)
if(originalImage.bounds[1]%2==1) originalImage.translate(0,1)

//resize back down, now that we've dodged photoshop's stupidity with rotating odd-pixel-sized images
w = app.activeDocument.width;
h = app.activeDocument.height;
doc.resizeImage(UnitValue(w/2,"px"),UnitValue(h/2,"px"),null,ResampleMethod.NEARESTNEIGHBOR);

var pngFile = File("C:/Users/YOURUSERNAME/Documents/ConvertedRMMV/" +"$B_" + creatureName + "(f1).png");
pngSaveOptions = new PNGSaveOptions();
doc.saveAs(pngFile, pngSaveOptions, true, Extension.LOWERCASE);

app.preferences.rulerUnits = savedRuler;
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
Yanfly's event copier has a typo in the post copy code, which makes it never execute (and the pre copy code gets run twice)

The plugin parameters read
Code:
Yanfly.Param.EventCopierPostCopy = JSON.parse(Yanfly.Parameters['PreCopyCode']);
but should read
Code:
Yanfly.Param.EventCopierPostCopy = JSON.parse(Yanfly.Parameters['PostCopyCode']);
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
I was noticing that multiframe walk animations occasionally reset when using mog's chrono engine - so a 10 frame one would go 0-9,0-9,0-9, but occasionally just go 0-9,0-2,0-9 or 0-9,0-6,0-9 and then reset.

The issue seems to be that the animation count gets incremented incorrectly when speeding up animations with (s1) or (s2) or whatever.

By default RMMV increments by 1.5x when walking compared to static, and MOG_Charposes just adds a static amount to the animation count (rather than adding 1.5x when walking. I added this into MOG_CharPoses and it appears to fix it.

Code:
Game_CharacterBase.prototype.updateAnimationCount = function() {
	_mog_charPose_charBase_updateAnimationCount.call(this);
    this._animationCount += this._frames.speed;
	
	//edited by restart to make it consistent with moving being faster
	if (this.isMoving() && this.hasWalkAnime()) {
        this._animationCount += 0.5*this._frames.speed;
	}
};
So I think this was some kind of desync? If someone understands the engine better please fill me in, it's possible I'm just accidentally wallpapering over it.
 

Sanjha36

Villager
Member
Joined
Jul 16, 2019
Messages
5
Reaction score
1
First Language
English
Primarily Uses
RMMV
These are amazing! thanks so much! Please keep posting!!!
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
Unlocking Character Poses
One thing that was annoying about chrono engine was the limitation on character poses. By default it parses the first of a given tag in a character image filename, so having a character with nonstandard frames locks all the poses to be identical.

For instance, if you had $Harold(f6)(f2) as your protagonist, your idle pose can ONLY be named $Harold(f6)(f2)_idle, and so is forced to be a 6 frame pose going at 2x speed


These three functions, combined, remove that limit. Now you can put $Harold(f6)(f2)_idle(f4)(s1) into your folder, and harold will settle down to 1x speed when doing a 4-frame idle pose.


How does it work?

First, the initialize tweak means that we create a full list of character names when the game first starts up.

The setPose tweak uses that character name list. It will still default to whatever exact pose you select, but if it doesn't find that EXACT pose, it will check to see if there's a character image which is that pose followed by some suffixes.

Finally, the setCharacterFrames tweaks the parsing on the filename tags, so it always picks the LAST one instead of the first one. So the pose's tags will override the main image name. As a bonus, it will also parse negative direction shifts as well as positive.

Code:
var _crossengine_game_sys_init = Game_System.prototype.initialize
Game_System.prototype.initialize = function() {
	 _crossengine_game_sys_init.call(this);
	//create a list of image files in our character directory.  
	// This is necessary for the automatic pose recognition
	// so for example you can have $Character(f6).png and $Character(f6)_idle(f3).png
	// and the default pose code to apply _idle will detect the right character name
	//
	this.crossEngine={};
	var fs = require ("fs");
	let dir = fs.readdirSync( './img/characters/' );
    this.crossEngine.characterNameList = dir.filter( elm => elm.match(new RegExp(`.*\.(png)`, 'ig')));
	//slice off the .png extension
	for (var index =0; index<this.crossEngine.characterNameList.length;index++)
	{
		this.crossEngine.characterNameList[index]=this.crossEngine.characterNameList[index].slice(0, -4);
	}

	
};


//==============================
// * From Mog_CharPoses.js
//==============================
// I have altered this with a fallback that CHECKS to see if a pose exists.
//if it DOES exist, then we proceed as normal.
//if it does NOT exist, then we first check to see if there's a pose with different
//parameters (like _idle(f3) instead of just _idle
//if that exists, then we use that one instead
//if it does not exist, we don't load anything
//and whine in the console about it

//BASICALLY:
// set your frames and speed and y offset and all that jazz
// at the end of your original image name
// AND at the end of any poses
// and everything will be fine and dandy
// if you have multiple versions of a pose file I think it PROBABLY just picks
// whichever is alphabetically first if you don't specify the exact file name
// but honestly that's a weird edge case and if you REALLY want to get that effect
// you're better off manipulating the framerate or y offset or whatever dynamically
// instead of having seperate files

// ALSO: since this scans once when the game initializes
// if your image files change midgame the list will get stale
// but like
// don't do that, alright?  That's weird, and if you have something that complex
// you should just code your own system.
Game_CharacterBase.prototype.setPose = function() {
	 this._poses.idle[3] = false;
	 //default to keeping the same pose
	 var newPose=this._originalName.name;
	 if (this.isFaintPose()) {
		 newPose = this.setFaintPose();
     } else if (this.isKnockbackPose()) { 
	     newPose = this.setKnockbackPose();
     } else if (this.isGuardPose()) { 
	     newPose = this.setGuardingPose();		 
	 } else if (this.isActionPose()) {
		 newPose = this.setActionPose();
	 } else if (this.isVictoryPose()) {
		 newPose = this.setVictoryPose();	
	 } else if (this.isCastingPose()) {
		 newPose = this.setCastingPose();	
	 } else if (this.isAttackingPose()) {
		 newPose = this.setAttackingPose();			 	 	 
     } else if (this.isPickUPPose()) {
         newPose = this.setPickUPPose();
     } else if (this.isPushPullPose()) {
         newPose = this.setPushPullPose();	 
	 } else if (this.isDashingPose()) {
		 newPose = this.setDashPose();
	 } else if (this.isJumpingPose()) {
         newPose = this.setJumpPose();
	 } else if (this.isIdlePose()) {
	     newPose = this.setIdlePose();	 
	 };
	 // this code didn't do anything in stock chrono engine.  Mog was probably
	 // planning to expand this.  Commented out for now
	 /* if (this.isDiagonalDefaultPose()) {
		 newPose = this.setDiagonalDefaultPose();
	 } else {
         newPose = this._originalName.name;
     }; */
	

	//if the pose exists, return with it.  Otherwise we will have to see if the
	//parameters are different
	var fs = require ("fs");
    if ( fs.existsSync("./img/characters/" + newPose+'.png'))
	{
		//console.log(newPose)
		return newPose
	}else{
		for (var index =0; index<$gameSystem.crossEngine.characterNameList.length;index++)
		{
			if ($gameSystem.crossEngine.characterNameList[index].startsWith (newPose))
			{
				return $gameSystem.crossEngine.characterNameList[index];
			}
		}
		console.log('Could not find '+newPose+', using base image.')
		return this._originalName.name
	}
		
};

//From MOG_CharPoses
//==============================
// * Set Character Frames
//==============================
// I have edited this so you can have both negative offsets for x and y
// AND it now checks the LAST offset, not the first one.
// this means that if you have a pose with a different number of frames
// than the default, it'll pick up on that!
// for example, if the base character is Bob(f3), you can now have
// Bob(f3)_punch(f12)
Game_Character.prototype.setCharacterFrames = function() {
	this.clearCharacterFrames();
	var frames = this._characterName.match(/(\(F(\d+\.*\d*))/gi)
	if (frames) {
	   this._frames.enabled = true;
	   this._frames.index = 0;
	   this._frames.max = Number(frames[frames.length-1].match(/\d+/i));
	}
		//edited to include support for negative offsets
	var ex = this._characterName.match(/(X(-?\d+\.*\d*))/gi)
	if (ex) {this._frames.x = Number(ex[ex.length-1].match(/-?\d+/i))};
	var ey = this._characterName.match(/(Y(-?\d+\.*\d*))/gi)
	if (ey) {this._frames.y = Number(ey[ey.length-1].match(/-?\d+/i))};
	var sp = this._characterName.match(/(S(\d+\.*\d*))/gi)
	if (sp) {this._frames.speed = Number(sp[sp.length-1].match(/\d+/i))};
	if (this._frames.enabled) {this._pattern = 0};
	this._pattern = this._frames.enabled ? 0 : 1;
};
 
Last edited:

exel.exe

Veteran
Veteran
Joined
Feb 7, 2014
Messages
67
Reaction score
18
First Language
español
Primarily Uses
dude that's awesome! meaby you can put idle animation in chrono engine for looks more like chrono instead of the walking anim, great work
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
This is a compatibility mod that should make chrono engine work with YEP_Event_Spawner.

I can't necessarily say this won't cause issues if you have a ton of stuff onscreen being created and deleted over and over, but at least in the short term with 60+ yep events getting spawned and despawned a couple times a second it seems to work just fine.


EDIT: DEPRECIATED, I have a better way of doing this in the next post

Code:
 /// Compatiblity mod for YEP_Event_Spawner
  // essentially the problem is that YEP assumes that events 1000+ are all reserved
  // for its own personal use
  // whereas Mog's code by default just pushes tools onto the event list
  // once a yanfly event is created that means that mog's will push into yanfly's reserved slots
  // basically yanfly makes event 1001, mog says 'Oh, okay, I'm on the end, lemme have 1002', then
  // yanfly drops the next spawned event into 1002 and overwrites what mog was making,
  // which means that the tools get erased, leaving only their sprites behind.
  //
  // this replaces the tool event spawn in chrono engine with one that checks to see if yanfly's event
  // spawner exists and has created things.  If it HAS, then the tool events gets registered with
  // yanfly's tracking system, so there's no conflict.
  //==============================
// * Add Tool Events
//==============================
Game_Map.prototype.addToolEvents = function() {
    var eventsTools = [];
	var mapId = Number(Moghunter.toolSys_MapID);
    $gameSystem._eventDataToolLoad = true;
    for (var i = 0; i < $gameSystem._eventDataTool.length; i++) {
		var actionId = $gameSystem._eventDataTool[i][0];
		var user = $gameSystem._eventDataTool[i][1];
        if ($dataMapTool.events[actionId]) {
            eventsTools[i] = new ToolEvent(mapId,actionId,user,false);
			if (eventsTools[i]._tool.active) {
				this._events.push(eventsTools[i])
			} else {
				eventsTools[i] = null;
			};
        };
		//check to see if yanfly's event spawner exists
		// and that it's created some spawned events
		// if it does, add our tool events to the list
		if((typeof(Yanfly.Param.EventSpawnerID)=="number"))
		{
			//check if we have more events than yanfly's spanwer id
			if (this._events.length>Yanfly.Param.EventSpawnerID-1)
			{
				var spawnId = this._events.length-1 //we pushed to the list so it's the last entry
				this._spawnedEvents[spawnId - Yanfly.Param.EventSpawnerID] =eventsTools[i];
			}
		}
	};
	$gameSystem._eventDataToolLoad = false;
	$gameSystem._eventDataTool = null;
	$gameSystem._eventDataToolRequestAddSprite = true;
	this.refresh();
};

//==============================
// * remove Tool Events
//==============================
Game_Map.prototype.removeToolEvents = function() 
{
    for (var i = 0; i < this._events.length; i++) 
	{
		var ev = this._events[i];
		if (ev && ev._tool.removeSprite) 
		{
	        this.removeRasEffectEvents(ev)
		    ev._tool.removeSprite = false;
		    this._events.splice(i,1);
		    this.removeToolsOnMap(ev._tool.id);
			if((typeof(Yanfly.Param.EventSpawnerID)=="number"))
			{
				//check if we have more events than yanfly's spanwer id
				if (this._events.length>Yanfly.Param.EventSpawnerID-1)
				{
					this._spawnedEvents[i - Yanfly.Param.EventSpawnerID] = null;
				}
			}
		};
	};
};
 
Last edited:

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
Here's the sprite cleanup routine which helps YEP_EventSpawner performance significantly, without changing anything else under the hood. I also have a more comprehensive tuneup afterwards.
Derping around with it some more, I think I've managed to reduce the performance degradation of YEP_EventSpawner when you create or destroy large numbers of events. It seems that events which get erased/despawned end up still getting looped into the sprite update routine, which means that these invisible ghost sprites start sucking up all your frames.

Code:
 Yanfly.SpawnEvent = function(mapId, eventId, x, y, preserved) {
  		//there's a performance issue with yanfly's event spawner due to how rpgmaker just 'hides' erased events rather than actually removing them.
		// I am going to take a page out of mog's book
		// and actually erase for real any erased spawned event
	for (var i = 0; i < SceneManager._scene._spriteset._characterSprites.length; i++) {
		var char = SceneManager._scene._spriteset._characterSprites[i];
		if (char && char._character._erased) {
			SceneManager._scene._spriteset._tilemap.removeChild(char);
			char._character.endAnimation();
			char._character.erase();
		};        
    };
		
	if ($gameParty.inBattle()) return;
	if (Yanfly.SpawnEventFailChecks(mapId, eventId, x, y)) return;
	preserved = preserved || false;
	$gameMap.spawnEvent(mapId, eventId, x, y, preserved);
};
I ended up basing this additional code heavily off of mog's chrono engine tool system, since he fully deletes all the bullets and sprites he spawns. So far, this is a half-measure - if you leave it creating and destroying events constantly it will EVENTUALLY still slow down, since the $gameMap.events() gets clogged with ghost events. (I'm trying to figure out if I can do a tweak to avoid that without weird side effects, but we'll see if that works)

I'm triggering the event sprite cleanup in the spawn new event function since it'll get called most frequently when you have the most events onmap.


That's the old version, here's the new one with further performance improvements and better moghunter chrono engine compatibility.

Yanfly Event Spawner Performance Patch / Compatibility with Moghunter's Chrono Engine Patch

This patch reworks the logic in how yanfly's event spawner picks where events go in the event list.

Previously, yanfly operated under an offset + concatenation system. A newly created event would be added to the list of spawned events, and then onto its slot in the gamemap event list (so since the default offset is 1000, the 20th event you spawn would go into slot 1020).

The problem with moghunter's code is that mog just slaps his events right onto the end of the list (so they get overwritten by yanfly events).

The performance problem with yanfly's code is that erased event bloat can result in massive lag. I did a test by repeatedly spawning and erasing events, with only one or two real events onscreen at a given time. Stock eventspawner will get to the single digit FPS after about erased 2,500 events on my computer. This patch gets basically no performance loss at 13,000. There's still a little, but it's significantly better(I THINK the next bottleneck is in some sort of sprite-side code, since opening/closing the menu helps a lot).


What this patch does is two things. First, it prunes dead events. Second, it forces reuse of dead spawned events. If you erase event 1001, when you try to spawn your next event, the newly spawned event will take slot 1001. This means that the game doesn't loop through tons and tons of dead events.

Events marked as 'persistent' will not get cleaned up even if they are erased. They still count towards the cap of 1000 events.

Now, the downside is that this puts a hard cap on the number of spawned events that you can have at once - this is configurable in code and I put it at 1000 by default. By having both a minimum and maximum event Id for yanfly events, I can safely ensure that mog's tool events always get added at the end of the event list without overlapping. Since inactive events get wiped, the only way you'll hit that cap is if you have 1000 active spawned events on the same map at once, in which case you probably have other things you want to optimise.

We also can no longer say that every entry in _spawnedEvents has an index in ._events that's 1000 higher (for instance, if we created two events, #1 and #2, then erased #1 and created a third, #3, then $gameMap._spawnedEvents would have [#2, #3], but ._events would have [#3,#2]) Because of that, I have added a $gameMap._spawnedEventIds list. This maps 1:1 on to ._spawnedEvents, and shows where they go in ._events. Technically speaking this shouldn't be necessary (we can pull ._eventId ), but it makes it easier for me to write code and I'm lazy.

Both these lists only track active events - inactive events get pruned out of them. Since spawned events get added onto the end, these are chronological with oldest first.

The only weird edge case I can think of is that reusing eventIds means that if you save a spawned eventId to a variable, it'll now end up pointing to some random newly spawned event, instead of to a dead event.


Yanfly Event Spawner Performance Patch / Compatibility with Moghunter's Chrono Engine Patch Code
Code:
//Yanfly Event Spawner Performance Patch / Compatibility with Moghunter's Chrono Engine Patch
//



//look, if you have more than a thousand events spawned onscreen
// at once, and the performance is still good?  You obviously
// know better than me how to optimize this stuff.
var CrossEngine = CrossEngine || {};
CrossEngine.MaxEventSpawn = 1000;

 Yanfly.SpawnEvent = function(mapId, eventId, x, y, preserved) {
  		//there's a huge performance issue with yanfly's event spawner due to how rpgmaker just 'hides' erased events rather than actually removing them.
		// I am going to take a page out of mog's book
		// and actually erase for real any erased spawned event
		
		//first the sprites
	for (var i = 0; i < SceneManager._scene._spriteset._characterSprites.length; i++) {
		var char = SceneManager._scene._spriteset._characterSprites[i];
		if (char && char._character._erased) {
			SceneManager._scene._spriteset._tilemap.removeChild(char);
			char._character.endAnimation();
			char._character.erase();
		};        
    };
		//then the actual events
 	for (var i = 0; i < $gameMap._spawnedEventIds.length; i++) 
	{
		
		var ev = $gameMap._events[$gameMap._spawnedEventIds[i]];
		if(ev&&ev._spawned && ev._erased &&!ev._spawnPreserved)
			{
				//wipe the event for reuse
				$gameMap._events[$gameMap._spawnedEventIds[i]]=undefined;
				//clear any lingering self switches
				var letters = ['A','B','C','D']
				for (var j = 0; j < letters.length; j++) 
				{
					var key = [$gameMap._mapId, $gameMap._spawnedEventIds[i], letters[j]];
					$gameSelfSwitches.setValue(key, false);
				}
				//excise from the list of spawned items
				$gameMap._spawnedEventIds.splice(i,1)
				$gameMap._spawnedEvents.splice(i,1)
				i--;
			}
	} 
	
	if ($gameParty.inBattle()) return;
	if (Yanfly.SpawnEventFailChecks(mapId, eventId, x, y)) return;
	preserved = preserved || false;
	$gameMap.spawnEvent(mapId, eventId, x, y, preserved);
};

  
Game_Map.prototype.spawnEvent = function(mapId, eventId, x, y, preserved) {
	var spawnId=-1;
	if (this._spawnedEvents.length<CrossEngine.MaxEventSpawn)
	{
		//scan through our yanfly reserved event ids to find an open slot
		for (var i = Yanfly.Param.EventSpawnerID; i < Yanfly.Param.EventSpawnerID+CrossEngine.MaxEventSpawn; ++i) {
			if(!$gameMap._events[i])
			{
				spawnId=i;
				break
			}
		}
		if (spawnId==-1)
		{
			if ($gameTemp.isPlaytest()) 
			{
				console.log('all events are full, even though we SHOULD have checked that')
			}
			return;
		}
	}else{
		//if we have our maximum number of spawned events
		// and they ALL exist, then throw an error to the console and abort
		if ($gameTemp.isPlaytest()) 
		{
			console.log('We have reached the maximum of '+CrossEngine.MaxEventSpawn+" spawned events!");
		}
		return;
	}
	
	$gameTemp._SpawnData = {
	baseMapId: this.mapId(),
	spawnId: spawnId,
	mapId: mapId,
	eventId: eventId, 
	x: x, 
	y: y,
	preserved: preserved
	};
	var spawnedEvent = new Game_Event(mapId, eventId);
	this._events[spawnId]=spawnedEvent;
	this._spawnedEvents.push(spawnedEvent); //
	this._spawnedEventIds.push(spawnId);
	
	this._cumulativeSpawnedEvents+=1;
	//this._spawnedEvents[spawnId - Yanfly.Param.EventSpawnerID] = spawnedEvent;

	$gameTemp._SpawnData = undefined;
};

// change the event Id check to use our new list
Yanfly.DespawnEventID = function(eventId) {
  if ($gameMap._spawnedEventIds.indexOf(eventId) == -1) {
    if ($gameTemp.isPlaytest()) {
      console.log('Event ID ' + eventId + ' is not a valid spawned event ID.');
      return;
    }
  }
  $gameMap.despawnEventId(eventId);
};

Yanfly.ClearSpawnedEvents = function(mapId) {
  mapId = mapId || $gameMap.mapId();
  var data = $gameSystem.getMapSpawnedEventData(mapId);
  for (var i = 1; i < data.length; i=i+1) {
    var eventData = data[i];
    if (!eventData) continue;
    var eventId = eventData.eventId();
    if (mapId === $gameMap.mapId()) {
      Yanfly.DespawnEventID(eventId);
	  i=i-1; // if we splice them out we gotta reduce our counter, 
			 // since the entire list of events is getting shorter
	  } else {
      data.splice(i,1);
	  i=i-1;
    }
  }
};


//=============================================================================
// Game_System
//=============================================================================


Game_System.prototype.removeTemporaryMapSpawnedEvents = function(mapId) {
  var data = this.getMapSpawnedEventData(mapId);
  for (var i = 1; i < data.length; ++i) {
    var eventData = data[i];
    if (!eventData) continue;
    if (eventData._spawnPreserved) continue;
		var letters = ['A','B','C','D']
		for (var j = 0; j < letters.length; j++) 
		{
			var key = [$gameMap._mapId, eventData._eventId, letters[j]];
			$gameSelfSwitches.setValue(key, false);
		}
    data.splice(i,1); //just delete them instead of nulling them
	i=i-1;
  }
};

//=============================================================================
// Game_Map
//=============================================================================
CrossEngine_Yanfly_EventSpawn_Game_Map_setup = Game_Map.prototype.setup;
Game_Map.prototype.setup = function(mapId) {
  CrossEngine_Yanfly_EventSpawn_Game_Map_setup.call(this, mapId);
  // extend the event array out to our whole block of yanfly-reserved events.
  // this ensures mog's stuff will never mix, because he appends his tool events
  // after the overall event list.
  this._events[CrossEngine.MaxEventSpawn+Yanfly.Param.EventSpawnerID+1]=undefined;
};

Game_Map.prototype.setupSpawnedEvents = function(mapId) {
  if (mapId !== this.mapId() && $gamePlayer) {
    $gameSystem.removeTemporaryMapSpawnedEvents(this.mapId())
  }
  this._spawnedEvents = $gameSystem.getMapSpawnedEventData(mapId);
  this._spawnedEventIds=[];
  this._cumulativeSpawnedEvents=0;  //tracks how many we've spawned total (including erased ones).
};

Game_Map.prototype.restoreSpawnedEvents = function() {
  var length = this._spawnedEvents.length;
  for (var i = 0; i < length; ++i) {
    var spawnedEvent = this._spawnedEvents[i];
	this._spawnedEventIds[i]=null;
    if (!spawnedEvent) continue;
    this._events[i + Yanfly.Param.EventSpawnerID] = spawnedEvent;
	this._spawnedEventIds[i]=i + Yanfly.Param.EventSpawnerID;
	this._events[i + Yanfly.Param.EventSpawnerID]._eventId = i + Yanfly.Param.EventSpawnerID;// make sure it knows what its new id is
    spawnedEvent._pageIndex = -2;
    this._needsRefresh = true;
  }
};


Game_Map.prototype.despawnEventId = function(eventId) {
	//change the check to see if it's in our list
  if (this._spawnedEventIds.indexOf(eventId) == -1) {return;}
  if (!this._spawnedEvents) return;
  var ev = this.event(eventId);
  ev.locate(-1, -1);
  this.eraseEvent(eventId);
  this._spawnedEvents.splice(this._spawnedEventIds.indexOf(eventId),1);
  this._spawnedEventIds.splice(this._spawnedEventIds.indexOf(eventId),1);
  this._events[eventId]=undefined;
};
 
Last edited:

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
The original rotation tweak requires chrono engine to work.

This little script pile bundles in the functions from chrono engine that are required to make rotation work, in case you have both MOG_CharacterMotion and Mog_CharacterPoses but don't want the full chrono engine experience. If you want to do something like have a sprite rotate forever, this is your script!

Chrono-less Rotation Tweak
Code:
var spinningisCool = Game_CharacterBase.prototype.initMembers;
Game_CharacterBase.prototype.initMembers = function() {
	this._user = {};
	this._user.rotation = [0,0];
	this._user.treasure = [null,false,0,0,0];
	spinningisCool.call(this);
	
}


//==============================
// * set Angle
//==============================
Game_CharacterBase.prototype.setAngle = function(angle) {
	var ag = angle * Math.PI / 180;
    this._user.rotation = [ag,angle];
};


//==============================
// * motionR
//==============================
Game_CharacterBase.prototype.motionR = function() {
	var n = this._rotationData[0] + this._swingData[1]
	
	n += this._user.rotation[0];
	
	return n;
};

Game_CharacterBase.prototype.setTargetAngle = function(targetAngle) {
   var ag = targetAngle * Math.PI / 180;
    this._user.targetAngle = [ag,targetAngle];
};

Game_CharacterBase.prototype.setRotationRate = function(rotationSpeed) {
   var ag = rotationSpeed * Math.PI / 180;
    this._user.rotationSpeed = [ag,rotationSpeed];
};

Game_CharacterBase.prototype.setRotationFlexible = function() {
   //flag to say we can go clockwise OR counterclockwise, whichever is closer
    this._user.rotationFlexible= true;
};

Game_CharacterBase.prototype.clearRotationRate = function() {
   this._user.targetAngle =null;
    this._user.rotationSpeed = null;
   this._user.rotationFlexible= false;

};




Sprite_Character.prototype.updateCharPosesPosition = function() {
	//honestly I don't understand what this is supposed to be doing, and a
	//constant offset for the x displacement fits what I want to do,
	//I've removed the x-offset shifting, to make way for my stuff
	//sorry if this breaks stuff for you!
	
	//if (this._character.direction() === 4 || this._character.direction() === 6) {	
	//	var ex = this._character.direction() === 6 ? this._character._frames.x : -this._character._frames.x;
    //} else {
	//	var ex = 0;
	//};


	var ex = this._character._frames.x;
	var ey = this._character._frames.y;	
	
	// add on additional rotation specified in tool
	if (this._character._tool)
	{
		if(this._character._tool.offsetX)
		{
			ex+=this._character._tool.offsetX;
		}
		if(this._character._tool.offsetY)
		{
			ey+=this._character._tool.offsetY;
		}
		if(this._character._tool.diagonalAngle)
		{
			this._character._user.rotation = [this._character._tool.diagonalAngle,Math.degrees(this._character._tool.diagonalAngle)];
		}
	}
	

	ag=0;
	angle=0;

	thisHasRotationUser=false;
	thisHasRotationChar=false;
	//technically this isn't the 'correct' way of checking to see
	//if we have this trait, but there shouldn't be any edge cases that matter
	//since if rotation has a value but it resolves as 'false' we don't want to
	//rotate after all
	if (this._character._user.rotation)
	{
		thisHasRotationChar=true;
		[ag,angle] = this._character._user.rotation;
	}else{
		if ($gameMap.event(3)._user.rotation[0] =undefined)
		{
			$gameMap.event(3)._user.rotation[0]=0;
			$gameMap.event(3)._user.rotation[1]=0;
		}
		
	}
	


	if (thisHasRotationChar)
	{
		var isSpinning=false;
		var hasTargetAngle=false;
		if(thisHasRotationChar)
		{
			if (this._character._user.rotationSpeed)
			{
				[agSpeed,angleSpeed] = this._character._user.rotationSpeed;
				isSpinning=true;
			}
			if (this._character._user.targetAngle || this._character._user.targetAngle ==0	)
			{
				[agTarget,angleTarget] = this._character._user.targetAngle;
				hasTargetAngle=true;
				
			}
			if (isSpinning){
				if(hasTargetAngle){
					//if our target angle is within a single step, snap to it.
					if(Math.abs(((((angleTarget+360) % 360)-((angle+360) % 360))/angleSpeed))<1){
						this._character.setAngle(angleTarget);
					}else{
						//if we are allowed to go in either direction, pick the shorter distance
						if(this._character._user.rotationFlexible)
						{
							if (((((angleTarget+360) % 360)-((angle+360) % 360) +360) % 360)>180)
								this._character.setAngle(angle-Math.abs(angleSpeed));
							else
								this._character.setAngle(angle+Math.abs(angleSpeed));
						}else{
							this._character.setAngle(angle+angleSpeed);
						}
					}
				}else{
					//if we don't have a target angle, but do have a rotation speed, keep spinning without limit!
					this._character.setAngle(angle+angleSpeed);
				}
			}
		}else if(thisHasRotationUser){
			if (this._user.rotationSpeed) 
			{
				[agSpeed,angleSpeed] = this._user.rotationSpeed;
				isSpinning=true;
			} 
			if (this._user.targetAngle) 
			{
				[agTarget,angleTarget] = this._user.targetAngle;
				hasTargetAngle=true;
			}
			if (isSpinning){
				if(hasTargetAngle){
					//if our target angle is within a single step, snap to it.
					if(Math.abs(((((angleTarget+360) % 360)-((angle+360) % 360))/angleSpeed))<1){
						this.setAngle(angleTarget);
					}else{
						if(this._user.rotationFlexible)
						{
							if (((((angleTarget+360) % 360)-((angle+360) % 360) +360) % 360)>180)
								this.setAngle(angle-Math.abs(angleSpeed));
							else
								this.setAngle(angle+Math.abs(angleSpeed));
						}else{
							this.setAngle(angle+angleSpeed);
						}
					}
				}else{
					//if we don't have a target angle, but do have a rotation speed, keep spinning without limit!
					this.setAngle(angle+angleSpeed);
				}
			}
		}

		
		//update ag and angle
		if (this._character._user.rotation || (this._character._user.rotation==0))
		{
			[ag,angle] = this._character._user.rotation;
		}
	}
	if (ag==0)
	{
	this.x += ex;
	this.y += ey;
	
	}else{
	//I have altered this code to
	//rotate events around the displacement point by default, 
	//instead of the base of the sprite	
	//technically this rotation is only off by a pixel or two, but w/e, close enough.
	baselength=Math.sqrt(ex*ex + ey*ey);
	

	this.x += ex*Math.cos(ag) - ey * Math.sin(ag);
	this.y += ex*Math.sin(ag) + ey * Math.cos(ag);

	}
	
};

//one problem with the above is that animations end up vertically displaced
//from their targets!  And animations are attached to a single point on the 
//sprite, which means if it spins, they spin!
//this restores how animations normally work
_spin_sprite_animation_update_position=Sprite_Animation.prototype.updatePosition;
Sprite_Animation.prototype.updatePosition = function()
{
		_spin_sprite_animation_update_position.call(this);
		if (this._target._character)
		{
			//if we're targeting a sprite on a character
			//then
			var ex =0;
			var ey =0;
			
			if (this._target._character._frames.x)
			{
				ex+=this._target._character._frames.x;
			}
			if (this._target._character._frames.y)
			{
				ey+=this._target._character._frames.y;
			}
			if (this._target._character._tool)
			{
				if(this._target._character._tool.offsetX)
				{
					ex+=this._target._character._tool.offsetX;
				}
				if(this._target._character._tool.offsetY)
				{
					ey+=this._target._character._tool.offsetY;
				}
			}
			var ag=0;
			var angle=0;
			if (this._target._character._user.rotation || (this._target._character._user.rotation==0))
			{
				[ag,angle] = this._target._character._user.rotation;
			}
			
			this.x -= ex*Math.cos(ag) - ey * Math.sin(ag) -ex;
			this.y -= ex*Math.sin(ag) + ey * Math.cos(ag) -ey*1.5;
		}
		
}
 
Last edited:

spitzfire1985

BZ Gaming
Veteran
Joined
Sep 4, 2019
Messages
110
Reaction score
32
First Language
English
Primarily Uses
RMMV
Unlocking Character Poses
One thing that was annoying about chrono engine was the limitation on character poses. By default it parses the first of a given tag in a character image filename, so having a character with nonstandard frames locks all the poses to be identical.

For instance, if you had $Harold(f6)(f2) as your protagonist, your idle pose can ONLY be named $Harold(f6)(f2)_idle, and so is forced to be a 6 frame pose going at 2x speed


These three functions, combined, remove that limit. Now you can put $Harold(f6)(f2)_idle(f4)(s1) into your folder, and harold will settle down to 1x speed when doing a 4-frame idle pose.


How does it work?

First, the initialize tweak means that we create a full list of character names when the game first starts up.

The setPose tweak uses that character name list. It will still default to whatever exact pose you select, but if it doesn't find that EXACT pose, it will check to see if there's a character image which is that pose followed by some suffixes.

Finally, the setCharacterFrames tweaks the parsing on the filename tags, so it always picks the LAST one instead of the first one. So the pose's tags will override the main image name. As a bonus, it will also parse negative direction shifts as well as positive.

Code:
var _crossengine_game_sys_init = Game_System.prototype.initialize
Game_System.prototype.initialize = function() {
	 _crossengine_game_sys_init.call(this);
	//create a list of image files in our character directory.  
	// This is necessary for the automatic pose recognition
	// so for example you can have $Character(f6).png and $Character(f6)_idle(f3).png
	// and the default pose code to apply _idle will detect the right character name
	//
	this.crossEngine={};
	var fs = require ("fs");
	let dir = fs.readdirSync( './img/characters/' );
    this.crossEngine.characterNameList = dir.filter( elm => elm.match(new RegExp(`.*\.(png)`, 'ig')));
	//slice off the .png extension
	for (var index =0; index<this.crossEngine.characterNameList.length;index++)
	{
		this.crossEngine.characterNameList[index]=this.crossEngine.characterNameList[index].slice(0, -4);
	}

	
};


//==============================
// * From Mog_CharPoses.js
//==============================
// I have altered this with a fallback that CHECKS to see if a pose exists.
//if it DOES exist, then we proceed as normal.
//if it does NOT exist, then we first check to see if there's a pose with different
//parameters (like _idle(f3) instead of just _idle
//if that exists, then we use that one instead
//if it does not exist, we don't load anything
//and whine in the console about it

//BASICALLY:
// set your frames and speed and y offset and all that jazz
// at the end of your original image name
// AND at the end of any poses
// and everything will be fine and dandy
// if you have multiple versions of a pose file I think it PROBABLY just picks
// whichever is alphabetically first if you don't specify the exact file name
// but honestly that's a weird edge case and if you REALLY want to get that effect
// you're better off manipulating the framerate or y offset or whatever dynamically
// instead of having seperate files

// ALSO: since this scans once when the game initializes
// if your image files change midgame the list will get stale
// but like
// don't do that, alright?  That's weird, and if you have something that complex
// you should just code your own system.
Game_CharacterBase.prototype.setPose = function() {
	 this._poses.idle[3] = false;
	 //default to keeping the same pose
	 var newPose=this._originalName.name;
	 if (this.isFaintPose()) {
		 newPose = this.setFaintPose();
     } else if (this.isKnockbackPose()) { 
	     newPose = this.setKnockbackPose();
     } else if (this.isGuardPose()) { 
	     newPose = this.setGuardingPose();		 
	 } else if (this.isActionPose()) {
		 newPose = this.setActionPose();
	 } else if (this.isVictoryPose()) {
		 newPose = this.setVictoryPose();	
	 } else if (this.isCastingPose()) {
		 newPose = this.setCastingPose();	
	 } else if (this.isAttackingPose()) {
		 newPose = this.setAttackingPose();			 	 	 
     } else if (this.isPickUPPose()) {
         newPose = this.setPickUPPose();
     } else if (this.isPushPullPose()) {
         newPose = this.setPushPullPose();	 
	 } else if (this.isDashingPose()) {
		 newPose = this.setDashPose();
	 } else if (this.isJumpingPose()) {
         newPose = this.setJumpPose();
	 } else if (this.isIdlePose()) {
	     newPose = this.setIdlePose();	 
	 };
	 // this code didn't do anything in stock chrono engine.  Mog was probably
	 // planning to expand this.  Commented out for now
	 /* if (this.isDiagonalDefaultPose()) {
		 newPose = this.setDiagonalDefaultPose();
	 } else {
         newPose = this._originalName.name;
     }; */
	

	//if the pose exists, return with it.  Otherwise we will have to see if the
	//parameters are different
	var fs = require ("fs");
    if ( fs.existsSync("./img/characters/" + newPose+'.png'))
	{
		//console.log(newPose)
		return newPose
	}else{
		for (var index =0; index<$gameSystem.crossEngine.characterNameList.length;index++)
		{
			if ($gameSystem.crossEngine.characterNameList[index].startsWith (newPose))
			{
				return $gameSystem.crossEngine.characterNameList[index];
			}
		}
		console.log('Could not find '+newPose+', using base image.')
		return this._originalName.name
	}
		
};

//From MOG_CharPoses
//==============================
// * Set Character Frames
//==============================
// I have edited this so you can have both negative offsets for x and y
// AND it now checks the LAST offset, not the first one.
// this means that if you have a pose with a different number of frames
// than the default, it'll pick up on that!
// for example, if the base character is Bob(f3), you can now have
// Bob(f3)_punch(f12)
Game_Character.prototype.setCharacterFrames = function() {
	this.clearCharacterFrames();
	var frames = this._characterName.match(/(\(F(\d+\.*\d*))/gi)
	if (frames) {
	   this._frames.enabled = true;
	   this._frames.index = 0;
	   this._frames.max = Number(frames[frames.length-1].match(/\d+/i));
	}
		//edited to include support for negative offsets
	var ex = this._characterName.match(/(X(-?\d+\.*\d*))/gi)
	if (ex) {this._frames.x = Number(ex[ex.length-1].match(/-?\d+/i))};
	var ey = this._characterName.match(/(Y(-?\d+\.*\d*))/gi)
	if (ey) {this._frames.y = Number(ey[ey.length-1].match(/-?\d+/i))};
	var sp = this._characterName.match(/(S(\d+\.*\d*))/gi)
	if (sp) {this._frames.speed = Number(sp[sp.length-1].match(/\d+/i))};
	if (this._frames.enabled) {this._pattern = 0};
	this._pattern = this._frames.enabled ? 0 : 1;
};
how do I add the sprite Frames unlock exactly? trying to upgrade my dash
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
just stick it in as a .js file and import it as a plugin
 

QuietPenguin

Veteran
Veteran
Joined
May 24, 2020
Messages
30
Reaction score
12
First Language
English
Primarily Uses
RMMV
Hey I have a quick question re: Yanfly / Chrono Engine compatibility:

I used your script to fix the bug where having Item Core enabled prevents the player from moving during a Chrono Engine battle. And it worked great -- thanks so much! But having Item Core enabled still seems to prevent me from selecting the "Attack" command in battle (a buzzer plays when I try to select it and nothing happens). The other default Chrono Engine commands ("Tech" and "Guard") work fine. Any idea why that might be or how to fix it?

Thanks!
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
Hey I have a quick question re: Yanfly / Chrono Engine compatibility:

I used your script to fix the bug where having Item Core enabled prevents the player from moving during a Chrono Engine battle. And it worked great -- thanks so much! But having Item Core enabled still seems to prevent me from selecting the "Attack" command in battle (a buzzer plays when I try to select it and nothing happens). The other default Chrono Engine commands ("Tech" and "Guard") work fine. Any idea why that might be or how to fix it?

Thanks!
Try turning off pick up and throw, and maybe try using a different weapon. - I've heard of this bug before but since it isn't appearing in my own project I don't really know what's causing it.
 

QuietPenguin

Veteran
Veteran
Joined
May 24, 2020
Messages
30
Reaction score
12
First Language
English
Primarily Uses
RMMV
Thanks!

In case anyone else with the same issue comes across this thread in the future, I seem to have figured out the problem by using a fix for a different compatibility problem someone was having with these two plugins: enabling "Midgame Note Parsing" in the Yanfly's Item Core. No idea what that is or why it works, but so far it does!
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
ah, that'd do it. I thought you had that on because if it's disabled normally shields don't work in chrono either
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
Have fixed a bug in my yep spawner tweak - self switches weren't being properly cleared when existing events were discarded upon leaving a map.
 

DTurtle

Villager
Member
Joined
Nov 23, 2016
Messages
13
Reaction score
0
Primarily Uses
N/A
These are great!! Do you happen to know of a way to enable diagonal movement frames? Unforunately, GALV's character frames aren't compatible with the Chrono Engine.
 

Restart

Veteran
Veteran
Joined
Mar 15, 2019
Messages
513
Reaction score
401
First Language
English
Primarily Uses
RMMV
These are great!! Do you happen to know of a way to enable diagonal movement frames? Unforunately, GALV's character frames aren't compatible with the Chrono Engine.
I have them working as part of cross engine, but I'm not sure how many functions you'd need to extract in order to get them to work - definitely Sprite_Character.prototype.characterPatternY and Game_Character.prototype.setCharacterFrames at a bare minimum.
 

DTurtle

Villager
Member
Joined
Nov 23, 2016
Messages
13
Reaction score
0
Primarily Uses
N/A
I have them working as part of cross engine, but I'm not sure how many functions you'd need to extract in order to get them to work - definitely Sprite_Character.prototype.characterPatternY and Game_Character.prototype.setCharacterFrames at a bare minimum.
That's exactly what I've been looking for, thank you!
 

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Posts

Latest Profile Posts

What are your experience on trying to boost a MV project on an Android 7+ phone to a constant 60 fps? It's very hard for me(mine is 720x1440 px with Octa-core 1.8 GHz Cortex-A53 and Adreno 506), especially with an ATB system plugin. The best minimum I can get is 30+ fps :)
MZ Spoiler Thread: Blah, blah, blah, blah...
Me: :kaoslp:
Mog: Star Ocean Battle system!
Me:
:MV1:
Hosting a private game jam...might have smthng to post soon if I can fix these dang parallaxes.
Oof it's been a while...well I'm back to stay!

Forum statistics

Threads
99,245
Messages
963,374
Members
130,829
Latest member
Fn02
Top