Jump to content

Release: Homunculus Storage


Tero

Recommended Posts


  • Group:  Members
  • Topic Count:  8
  • Topics Per Day:  0.02
  • Content Count:  54
  • Reputation:   106
  • Joined:  10/01/22
  • Last Seen:  

This is not tested under renewal.  I think it should still work, but test it with a GM character first.

 

The default implementation of Homunculi is kind of obnoxious.  When you create one, you get one of the homunculi at random, and in order to get another one, you have to completely delete them from existence.  Wouldn't it be nice if you could keep the other homunculi to use later?  Well, now you can!

 

EmbyroMod.png.e55978e9e9d6e87afd4d0252a4dfeba1.png

 

What it does:

- When you use rest, the Homunculus returns to its Embyro.

- You can use Call Homunculus to call it back out, just as it was when you rested it.

- You can store the Homunculus for later use, allowing you to raise a different Homunculus, which is not normally possible.

- Only you can call the Homunculus back out.  If another alchemist tries to call it, it doesn't let them.

- You can still delete the Homunculi as normal to remove them from existence completely.

 

How it works from a technical perspective:

- When you rest the homunculus, it is technically "removed" from you, similar to deleting it, but it returns an embyro with the Homunculus id set to its card[1] slot.

- When you call out a Homunculus from an Embyro, if the embyro has a value in its card[1] slot, it restores that Homunculus instead of creating a new one.

 

Some minor limitations:

- It's not possible to distinguish embryos from each other, and call homunculus will always try to call the first one it finds in your inventory.  You'll need to store the ones you don't want to raise right now, and it may take some trial and error to hatch the right one if you have a bunch.

- The name display of the embryo doesn't work with the client I have (it seems like the client will only display the name if the card[1] slot is 0, which it's not if a homunculus is in there).  This makes it hard to tell who owns a homunculus embyro.

 

How to implement:

Make the following code changes:

 

In homunculus.cpp:

Find the method "hom_vaporize":

First, add the following variable declarations to the top, under the existing ones:

	int itemflag;
	struct item tmp_item;

Next, comment out the following line:

hd->homunculus.vaporize = flag ? flag : HOM_ST_REST;

Then, insert the following at the end of the method, just before the return statement.

if (hd->homunculus.intimacy > 0) {	

		memset(&tmp_item, 0, sizeof(tmp_item));
		tmp_item.nameid = 7142;
		tmp_item.amount = 1;
		tmp_item.identify = 1;
		tmp_item.card[0] = CARD0_CREATE;
		tmp_item.card[1] = hd->homunculus.hom_id;
		tmp_item.card[2] = GetWord(sd->status.char_id, 0); // CharId
		tmp_item.card[3] = GetWord(sd->status.char_id, 1);

		if ((itemflag = pc_additem(sd, &tmp_item, tmp_item.amount, LOG_TYPE_PRODUCE))) {
			clif_additem(sd, 0, 0, itemflag);
			if (battle_config.skill_drop_items_full) {
				map_addflooritem(&tmp_item, tmp_item.amount, sd->bl.m, sd->bl.x, sd->bl.y, 0, 0, 0, 0, 0);
			}
		}

		unit_remove_map(&hd->bl, CLR_OUTSIGHT);
		sd->status.hom_id = 0;
		unit_free(&hd->bl, CLR_OUTSIGHT);
		sd->hd = 0;
		return 1;
	}

 

Next, find the method "hom_call":

Find this code:

if (!sd->status.hom_id) //Create a new homun.
		return hom_create_request(sd, HM_CLASS_BASE + rnd_value(0, 7)) ;

Replace it with this:

	struct item ed;
	int n;

	// find the embryo
	if (!sd->status.hom_id) { //Create or revive a homun.
		if ((n = pc_search_inventory(sd, 7142)) >= 0) {
			ed = sd->inventory.u.items_inventory[n];
		}
		else {
			return false; // no embryo
		}

		if (ed.card[1] != 0) {
			// is it ours?
			if (sd->status.char_id == MakeDWord(ed.card[2], ed.card[3])) {
				// revive the homun

				// delete the embryo
				pc_delitem(sd, n, 1, 0, 0, LOG_TYPE_CONSUME);

				sd->status.hom_id = ed.card[1];
				// proceed with rest of function
			}
			else {
				// Cannot revive someone else's homunculus
				return false;
			}			
		}
		else {
			// create a new homun

			// delete the embryo
			pc_delitem(sd, n, 1, 0, 0, LOG_TYPE_CONSUME);
			return hom_create_request(sd, HM_CLASS_BASE + rnd_value(0, 7));
		}
	}

 

Almost done, just one small change to make in skill.cpp.

 

Find this line:

	if (sd->status.hom_id) //Don't delete items when hom is already out.

And comment it out (we never want this method to delete the embyros, since the call method now handles it).

 

Now just recompile your server and you should have the new homunculus behaviour.

Edited by Tero
  • Upvote 1
  • Love 1
  • Like 1
Link to comment
Share on other sites

  • 5 months later...

  • Group:  Members
  • Topic Count:  8
  • Topics Per Day:  0.02
  • Content Count:  54
  • Reputation:   106
  • Joined:  10/01/22
  • Last Seen:  

If you've gotten annoyed with the limitation of not being able to see what homunculus is inside the Embryo, I've coded a significantly more complicated version of this change that displays a pet incubator-like dialog and lets you pick the embryo you want to hatch.  This is much more complex from a coding perspective though, so you should make sure you're confident in making changes to the codebase.

EmbyroMod2.png.a8912cd1fa29ed53a7643fd2aa8a37a2.png

Any embryos made with the old version of the mod will still work, but you'll have to hatch and rest them to convert them to their named versions.  This will also require you to distribute a new version of iteminfo.lub.

 

First, you need to add a new method to clif.hpp:

void clif_sendembryo(struct map_session_data* sd);

 

Next, you need to add the code for this method to clif.cpp:

/// Presents a list of embyros that can be revived
/// 01a6 <packet len>.W { <index>.W }*
void clif_sendembryo(struct map_session_data* sd)
{
	int i, n = 0, fd;

	nullpo_retv(sd);

	fd = sd->fd;

	WFIFOHEAD(fd, MAX_INVENTORY * 2 + 4);
	WFIFOW(fd, 0) = 0x1a6;
	for (i = 0, n = 0; i < MAX_INVENTORY; i++) {
		if (sd->inventory.u.items_inventory[i].nameid <= 0 || sd->inventory_data[i] == NULL ||
			(sd->inventory_data[i]->nameid != 7142 && !(sd->inventory_data[i]->nameid >= 9901 && sd->inventory_data[i]->nameid <= 9909)) ||
			sd->inventory.u.items_inventory[i].amount <= 0)
			continue;
		WFIFOW(fd, n * 2 + 4) = i + 2;
		n++;
	}
	WFIFOW(fd, 2) = 4 + n * 2;
	WFIFOSET(fd, WFIFOW(fd, 2));

	sd->menuskill_id = AM_CALLHOMUN;
	sd->menuskill_val = -1;
}

 

There's also a change needed in clif.cpp:

 

Find the method clif_parse_SelectEgg and replace it with this:

/// Answer to pet incubator egg selection dialog (CZ_SELECT_PETEGG).
/// 01a7 <index>.W
void clif_parse_SelectEgg(int fd, struct map_session_data* sd) {
	if (sd->menuskill_val != -1)
		return;

	if (sd->menuskill_id == SA_TAMINGMONSTER) {
		pet_select_egg(sd, RFIFOW(fd, packet_db[RFIFOW(fd, 0)].pos[0]) - 2);
		clif_menuskill_clear(sd);
	}

	if (sd->menuskill_id == AM_CALLHOMUN) {		
		hom_call(sd, RFIFOW(fd, packet_db[RFIFOW(fd, 0)].pos[0]) - 2);
		if (sd->pd) clif_send_petstatus(sd); // the client wipes the pet status upon answering this dialog, so it has to be resent.
		clif_menuskill_clear(sd);
	}
}

 

Now we need to change homunculus.hpp.

Find the definition for hom_call and change it to this:

bool hom_call(struct map_session_data *sd, short hom_index);

 

Now in homunculus.cpp:

Replace the hom_call method with this:

/**
 * Make a player spawn a homonculus (call)
 * @param sd
 * @param hom_index
 * @return False:failure, True:sucess
 */
bool hom_call(struct map_session_data* sd, short hom_index)
{
	struct homun_data* hd;
	struct item ed;
	int n;

	if (hom_index < 0 || hom_index >= MAX_INVENTORY)
		return 0; //Forged packet!	

	// find the embryo
	if (!sd->status.hom_id) { //Create or revive a homun.

		if (sd->inventory.u.items_inventory[hom_index].nameid == 7142 || sd->inventory.u.items_inventory[hom_index].nameid >= 9901 && sd->inventory.u.items_inventory[hom_index].nameid <= 9909)
			n = hom_index;
		else
		{ 
			ShowError("wrong embryo item inventory %d\n", hom_index);
			return false;
		}

		ed = sd->inventory.u.items_inventory[n];

		if (ed.card[1] != 0) {
			// is it ours?
			if (sd->status.char_id == MakeDWord(ed.card[2], ed.card[3])) {
				// revive the homun

				// delete the embryo
				pc_delitem(sd, n, 1, 0, 0, LOG_TYPE_CONSUME);

				sd->status.hom_id = ed.card[1];
				// proceed with rest of function
			}
			else {
				// Cannot revive someone else's homunculus
				return false;
			}			
		}
		else {
			// create a new homun

			// delete the embryo
			pc_delitem(sd, n, 1, 0, 0, LOG_TYPE_CONSUME);
			return hom_create_request(sd, HM_CLASS_BASE + rnd_value(0, 7));
		}
	}

	// If homunc not yet loaded, load it
	if (!sd->hd)
		return intif_homunculus_requestload(sd->status.account_id, sd->status.hom_id);

	hd = sd->hd;

	if (!hd->homunculus.vaporize)
		return false; //Can't use this if homun wasn't vaporized.

	if (hd->homunculus.vaporize == HOM_ST_MORPH)
		return false; // Can't call homunculus (morph state).

	hom_init_timers(hd);
	hd->homunculus.vaporize = HOM_ST_ACTIVE;
	if (hd->bl.prev == NULL)
	{	//Spawn him
		hd->bl.x = sd->bl.x;
		hd->bl.y = sd->bl.y;
		hd->bl.m = sd->bl.m;
		if(map_addblock(&hd->bl))
			return false;
		clif_spawn(&hd->bl);
		clif_send_homdata(sd,SP_ACK,0);
		clif_hominfo(sd,hd,1);
		clif_hominfo(sd,hd,0); // send this x2. dunno why, but kRO does that [blackhole89]
		clif_homskillinfoblock(sd);
		if (battle_config.hom_setting&HOMSET_COPY_SPEED)
			status_calc_bl(&hd->bl, SCB_SPEED);
		hom_save(hd);
	} else
		//Warp him to master.
		unit_warp(&hd->bl,sd->bl.m, sd->bl.x, sd->bl.y,CLR_OUTSIGHT);
	return true;
}

 

Also replace the hom_vaporize method with this:

/**
* Vaporize a character's homunculus
* @param sd
* @param flag 1: then HP needs to be 80% or above. 2: then set to morph state.
*/
int hom_vaporize(struct map_session_data *sd, int flag)
{
	struct homun_data *hd;
	int itemflag;
	struct item tmp_item;

	nullpo_ret(sd);

	hd = sd->hd;
	if (!hd || hd->homunculus.vaporize)
		return 0;

	if (status_isdead(&hd->bl))
		return 0; //Can't vaporize a dead homun.

	if (flag == HOM_ST_REST && get_percentage(hd->battle_status.hp, hd->battle_status.max_hp) < 80)
		return 0;

	hd->regen.state.block = 3; //Block regen while vaporized.
	//Delete timers when vaporized.
	hom_hungry_timer_delete(hd);
	//hd->homunculus.vaporize = flag ? flag : HOM_ST_REST;
	if (battle_config.hom_setting&HOMSET_RESET_REUSESKILL_VAPORIZED)
		memset(hd->blockskill, 0, sizeof(hd->blockskill));
	clif_hominfo(sd, sd->hd, 0);
	hom_save(hd);

	if (hd->homunculus.intimacy > 0) {	

		memset(&tmp_item, 0, sizeof(tmp_item));

		switch (hom_class2mapid(hd->homunculus.class_)) {
		case MAPID_LIF:
		case MAPID_LIF_E:
			tmp_item.nameid = 9901;
			break;
		case MAPID_AMISTR:
		case MAPID_AMISTR_E:
			tmp_item.nameid = 9902;
			break;
		case MAPID_FILIR:
		case MAPID_FILIR_E:
			tmp_item.nameid = 9903;
			break;
		case MAPID_VANILMIRTH:
		case MAPID_VANILMIRTH_E:
			tmp_item.nameid = 9904;
			break;
		case MAPID_EIRA:
			tmp_item.nameid = 9905;
			break;
		case MAPID_BAYERI:
			tmp_item.nameid = 9906;
			break;
		case MAPID_SERA:
			tmp_item.nameid = 9907;
			break;
		case MAPID_DIETER:
			tmp_item.nameid = 9908;
			break;
		case MAPID_ELANOR:
			tmp_item.nameid = 9909;
			break;
		}

		tmp_item.amount = 1;
		tmp_item.identify = 1;
		tmp_item.card[0] = CARD0_CREATE;
		tmp_item.card[1] = hd->homunculus.hom_id;
		tmp_item.card[2] = GetWord(sd->status.char_id, 0); // CharId
		tmp_item.card[3] = GetWord(sd->status.char_id, 1);

		if ((itemflag = pc_additem(sd, &tmp_item, tmp_item.amount, LOG_TYPE_PRODUCE))) {
			clif_additem(sd, 0, 0, itemflag);
			if (battle_config.skill_drop_items_full) {
				map_addflooritem(&tmp_item, tmp_item.amount, sd->bl.m, sd->bl.x, sd->bl.y, 0, 0, 0, 0, 0);
			}
		}

		unit_remove_map(&hd->bl, CLR_OUTSIGHT);
		sd->status.hom_id = 0;
		unit_free(&hd->bl, CLR_OUTSIGHT);
		sd->hd = 0;
		return 1;
	}

	return unit_remove_map(&hd->bl, CLR_OUTSIGHT);
}

 

Getting there, we now need to make a couple changes to skill.cpp:

As with the first version of this process, comment out this line:  (If you already implemented the first version this will be done already)

if (sd->status.hom_id) //Don't delete items when hom is already out.

 

Now find this code block:

case AM_CALLHOMUN:	//[orn]
		if (sd && !hom_call(sd))
			clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
		break;

 

And replace it with this:

case AM_CALLHOMUN:
		if (sd && !sd->status.hom_id) {
			clif_sendembryo(sd);
			clif_skill_nodamage(src, bl, skill_id, skill_lv, 1);
		}
		else if (sd && !hom_call(sd, 0)) {
			clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
		}
		break;

 

Now, find this line in script.cpp:

hom_call(sd); // Respawn homunculus.

 

and replace it with this:

hom_call(sd, 0); // Respawn homunculus.

 

Phew!  Done with code changes!  Now we just need to add the new embryos into the item-db (for a fun challenge, see if you can determine what their item ids are from the code before we proceed further).

 

If you have the old, text based item-db, you need to add this:

9901,Lif_Embryo,Lif Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9902,Amistr_Embryo,Amistr Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9903,Filir_Embryo,Filir Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9904,Vanilmirth_Embryo,Vanilmirth Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9905,Eira_Embryo,Eira Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9906,Bayeri_Embryo,Bayeri Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9907,Sera_Embryo,Sera Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9908,Dieter_Embryo,Dieter Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9909,Eleanor_Embryo,Eleanor Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}

 

If you have the new, yaml-based item-db, you need to add this to item-db-etc.yml

  - Id: 9901
    AegisName: Lif_Embryo
    Name: Lif Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9902
    AegisName: Amistr_Embryo
    Name: Amistr Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9903
    AegisName: Filir_Embryo
    Name: Filir Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9904
    AegisName: Vanilmirth_Embryo
    Name: Vanilmirth Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9905
    AegisName: Eira_Embryo
    Name: Eira Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9906
    AegisName: Bayeri_Embryo
    Name: Bayeri Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9907
    AegisName: Sera_Embryo
    Name: Sera Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9908
    AegisName: Dieter_Embryo
    Name: Dieter Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9909
    AegisName: Eleanor_Embryo
    Name: Eleanor Embryo
    Type: Etc
    Buy: 10
    Weight: 10

 

Finally, we just need to add the following to iteminfo.lub:

	[9901] = {
		unidentifiedDisplayName = "Lif Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Lif Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Lif.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9902] = {
		unidentifiedDisplayName = "Amistr Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Amistr Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing an Amistr.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9903] = {
		unidentifiedDisplayName = "Filir Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Filir Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Filir.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9904] = {
		unidentifiedDisplayName = "Vanilmirth Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Vanilmirth Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Vanilmirth.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9905] = {
		unidentifiedDisplayName = "Eira Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Eira Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing an Eira.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9906] = {
		unidentifiedDisplayName = "Bayeri Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Bayeri Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Bayeri.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9907] = {
		unidentifiedDisplayName = "Sera Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Sera Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Sera.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9908] = {
		unidentifiedDisplayName = "Dieter Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Dieter Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Dieter.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9909] = {
		unidentifiedDisplayName = "Eleanor Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Eleanor Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing an Eleanor.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},

 

And that should be everything!  Now just recompile the codebase and restart the server and you should have the new homunculus behaviour.  Hopefully I didn't miss anything in the steps.

Edited by Tero
  • Upvote 1
  • MVP 1
Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  74
  • Topics Per Day:  0.02
  • Content Count:  442
  • Reputation:   53
  • Joined:  08/28/12
  • Last Seen:  

At first, holy awesome work and thank you very much for sharing.
I tried to test it, but I got some errors after compiling. Do I miss something?!
image.png.86aa3aa2ec51ec860c3843a3ac995ff8.png

  • Upvote 1
Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  74
  • Topics Per Day:  0.02
  • Content Count:  442
  • Reputation:   53
  • Joined:  08/28/12
  • Last Seen:  

2 hours ago, Tero said:

Getting there, we now need to make a couple changes to skill.hpp:

As with the first version of this process, comment out this line:  (If you already implemented the first version this will be done already)

And for other ppl who want to use this, it's skill.cpp not skill.hpp xD
Little typo from this awesome guy. ^^

Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  8
  • Topics Per Day:  0.02
  • Content Count:  54
  • Reputation:   106
  • Joined:  10/01/22
  • Last Seen:  

44 minutes ago, WhiteEagle said:

At first, holy awesome work and thank you very much for sharing.
I tried to test it, but I got some errors after compiling. Do I miss something?!
image.png.86aa3aa2ec51ec860c3843a3ac995ff8.png

Looks like they've changed a couple small things in the code compared to the version I have, I remember why I posted it the way I did the first time now.  These kinds of errors typically occur when a method signature has changed, so you just need to swap the affected code with whatever they have in the base version.

 

To fix the first error, in hom_vamporize, find this:

if (battle_config.hom_setting&HOMSET_RESET_REUSESKILL_VAPORIZED)
		memset(hd->blockskill, 0, sizeof(hd->blockskill));

 

and change it to this:

if (battle_config.hom_setting&HOMSET_RESET_REUSESKILL_VAPORIZED) {
		hd->blockskill.clear();
		hd->blockskill.shrink_to_fit();
	}

 

For the second, find this in hom_call:

if (battle_config.hom_setting&HOMSET_COPY_SPEED)
			status_calc_bl(&hd->bl, SCB_SPEED);

and change it to this:

if (battle_config.hom_setting&HOMSET_COPY_SPEED)
			status_calc_bl(&hd->bl, { SCB_SPEED });

 

By the way, my version of hom_call is also missing this at the end, as it predates the update where this was done:

#ifdef RENEWAL
	sc_start(&sd->bl, &sd->bl, SC_HOMUN_TIME, 100, 1, skill_get_time(AM_CALLHOMUN, 1));
#endif

 

And similarly this is in hom_vaporize:

#ifdef RENEWAL
	status_change_end(&sd->bl, SC_HOMUN_TIME);
#endif

 

You can add this back in if you want (these are responsible for the Homunculi lasting only a certain amount of time).  I don't know if this plays nicely with this mod, though.

Edited by Tero
  • Upvote 2
  • Love 1
  • MVP 1
Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  74
  • Topics Per Day:  0.02
  • Content Count:  442
  • Reputation:   53
  • Joined:  08/28/12
  • Last Seen:  

First, thanks again for the release.
I have a few suggestions and I would be happy if these could be created and shared.

These are the things I noticed or what I think would improve it:
The select window should close after selecting a homunculus.

You could set a setting in battle/homunculus.conf, which sets the maximum number of homunculus that can be stored. For example that the player may have a maximum of 4 different homunculus. 

I found a bug, if you have more than one of the same embyros, it just comes out a random homunculus.
Also you can have multiple same homunculus, which should not be possible. I think the goal behind the storage system is that you can have all homunculus 1x and not as an example 4 same lifs.
(Example you have Lifs with Lv. 1 or Lv. 40 or Lv.88, which of the Lifs has now which level).
image.png.e1c9169b0e57419f355999bc1182d01c.png
Therefore the possibility would be practical to prevent that you can have more than one homunculus of the same kind.

That you don't get an item back in your inventory when you use "Rest". It might be read directly from the SQL database.
 

  • Upvote 1
Link to comment
Share on other sites

  • 3 weeks later...

  • Group:  Members
  • Topic Count:  1
  • Topics Per Day:  0.00
  • Content Count:  41
  • Reputation:   10
  • Joined:  08/22/22
  • Last Seen:  

On 4/16/2023 at 5:55 AM, Tero said:

If you've gotten annoyed with the limitation of not being able to see what homunculus is inside the Embryo, I've coded a significantly more complicated version of this change that displays a pet incubator-like dialog and lets you pick the embryo you want to hatch.  This is much more complex from a coding perspective though, so you should make sure you're confident in making changes to the codebase.

EmbyroMod2.png.a8912cd1fa29ed53a7643fd2aa8a37a2.png

Any embryos made with the old version of the mod will still work, but you'll have to hatch and rest them to convert them to their named versions.  This will also require you to distribute a new version of iteminfo.lub.

 

First, you need to add a new method to clif.hpp:

void clif_sendembryo(struct map_session_data* sd);

 

Next, you need to add the code for this method to clif.cpp:

/// Presents a list of embyros that can be revived
/// 01a6 <packet len>.W { <index>.W }*
void clif_sendembryo(struct map_session_data* sd)
{
	int i, n = 0, fd;

	nullpo_retv(sd);

	fd = sd->fd;

	WFIFOHEAD(fd, MAX_INVENTORY * 2 + 4);
	WFIFOW(fd, 0) = 0x1a6;
	for (i = 0, n = 0; i < MAX_INVENTORY; i++) {
		if (sd->inventory.u.items_inventory[i].nameid <= 0 || sd->inventory_data[i] == NULL ||
			(sd->inventory_data[i]->nameid != 7142 && !(sd->inventory_data[i]->nameid >= 9901 && sd->inventory_data[i]->nameid <= 9909)) ||
			sd->inventory.u.items_inventory[i].amount <= 0)
			continue;
		WFIFOW(fd, n * 2 + 4) = i + 2;
		n++;
	}
	WFIFOW(fd, 2) = 4 + n * 2;
	WFIFOSET(fd, WFIFOW(fd, 2));

	sd->menuskill_id = AM_CALLHOMUN;
	sd->menuskill_val = -1;
}

 

There's also a change needed in clif.cpp:

 

Find the method clif_parse_SelectEgg and replace it with this:

/// Answer to pet incubator egg selection dialog (CZ_SELECT_PETEGG).
/// 01a7 <index>.W
void clif_parse_SelectEgg(int fd, struct map_session_data* sd) {
	if (sd->menuskill_val != -1)
		return;

	if (sd->menuskill_id == SA_TAMINGMONSTER) {
		pet_select_egg(sd, RFIFOW(fd, packet_db[RFIFOW(fd, 0)].pos[0]) - 2);
		clif_menuskill_clear(sd);
	}

	if (sd->menuskill_id == AM_CALLHOMUN) {		
		hom_call(sd, RFIFOW(fd, packet_db[RFIFOW(fd, 0)].pos[0]) - 2);
		if (sd->pd) clif_send_petstatus(sd); // the client wipes the pet status upon answering this dialog, so it has to be resent.
		clif_menuskill_clear(sd);
	}
}

 

Now we need to change homunculus.hpp.

Find the definition for hom_call and change it to this:

bool hom_call(struct map_session_data *sd, short hom_index);

 

Now in homunculus.cpp:

Replace the hom_call method with this:

/**
 * Make a player spawn a homonculus (call)
 * @param sd
 * @param hom_index
 * @return False:failure, True:sucess
 */
bool hom_call(struct map_session_data* sd, short hom_index)
{
	struct homun_data* hd;
	struct item ed;
	int n;

	if (hom_index < 0 || hom_index >= MAX_INVENTORY)
		return 0; //Forged packet!	

	// find the embryo
	if (!sd->status.hom_id) { //Create or revive a homun.

		if (sd->inventory.u.items_inventory[hom_index].nameid == 7142 || sd->inventory.u.items_inventory[hom_index].nameid >= 9901 && sd->inventory.u.items_inventory[hom_index].nameid <= 9909)
			n = hom_index;
		else
		{ 
			ShowError("wrong embryo item inventory %d\n", hom_index);
			return false;
		}

		ed = sd->inventory.u.items_inventory[n];

		if (ed.card[1] != 0) {
			// is it ours?
			if (sd->status.char_id == MakeDWord(ed.card[2], ed.card[3])) {
				// revive the homun

				// delete the embryo
				pc_delitem(sd, n, 1, 0, 0, LOG_TYPE_CONSUME);
				clif_delitem(sd, n, 1, 3);

				sd->status.hom_id = ed.card[1];
				// proceed with rest of function
			}
			else {
				// Cannot revive someone else's homunculus
				return false;
			}			
		}
		else {
			// create a new homun

			// delete the embryo
			pc_delitem(sd, n, 1, 0, 0, LOG_TYPE_CONSUME);
			clif_delitem(sd, n, 1, 3);
			return hom_create_request(sd, HM_CLASS_BASE + rnd_value(0, 7));
		}
	}

	// If homunc not yet loaded, load it
	if (!sd->hd)
		return intif_homunculus_requestload(sd->status.account_id, sd->status.hom_id);

	hd = sd->hd;

	if (!hd->homunculus.vaporize)
		return false; //Can't use this if homun wasn't vaporized.

	if (hd->homunculus.vaporize == HOM_ST_MORPH)
		return false; // Can't call homunculus (morph state).

	hom_init_timers(hd);
	hd->homunculus.vaporize = HOM_ST_ACTIVE;
	if (hd->bl.prev == NULL)
	{	//Spawn him
		hd->bl.x = sd->bl.x;
		hd->bl.y = sd->bl.y;
		hd->bl.m = sd->bl.m;
		if(map_addblock(&hd->bl))
			return false;
		clif_spawn(&hd->bl);
		clif_send_homdata(sd,SP_ACK,0);
		clif_hominfo(sd,hd,1);
		clif_hominfo(sd,hd,0); // send this x2. dunno why, but kRO does that [blackhole89]
		clif_homskillinfoblock(sd);
		if (battle_config.hom_setting&HOMSET_COPY_SPEED)
			status_calc_bl(&hd->bl, SCB_SPEED);
		hom_save(hd);
	} else
		//Warp him to master.
		unit_warp(&hd->bl,sd->bl.m, sd->bl.x, sd->bl.y,CLR_OUTSIGHT);
	return true;
}

 

Also replace the hom_vaporize method with this:

/**
* Vaporize a character's homunculus
* @param sd
* @param flag 1: then HP needs to be 80% or above. 2: then set to morph state.
*/
int hom_vaporize(struct map_session_data *sd, int flag)
{
	struct homun_data *hd;
	int itemflag;
	struct item tmp_item;

	nullpo_ret(sd);

	hd = sd->hd;
	if (!hd || hd->homunculus.vaporize)
		return 0;

	if (status_isdead(&hd->bl))
		return 0; //Can't vaporize a dead homun.

	if (flag == HOM_ST_REST && get_percentage(hd->battle_status.hp, hd->battle_status.max_hp) < 80)
		return 0;

	hd->regen.state.block = 3; //Block regen while vaporized.
	//Delete timers when vaporized.
	hom_hungry_timer_delete(hd);
	//hd->homunculus.vaporize = flag ? flag : HOM_ST_REST;
	if (battle_config.hom_setting&HOMSET_RESET_REUSESKILL_VAPORIZED)
		memset(hd->blockskill, 0, sizeof(hd->blockskill));
	clif_hominfo(sd, sd->hd, 0);
	hom_save(hd);

	if (hd->homunculus.intimacy > 0) {	

		memset(&tmp_item, 0, sizeof(tmp_item));

		switch (hom_class2mapid(hd->homunculus.class_)) {
		case MAPID_LIF:
		case MAPID_LIF_E:
			tmp_item.nameid = 9901;
			break;
		case MAPID_AMISTR:
		case MAPID_AMISTR_E:
			tmp_item.nameid = 9902;
			break;
		case MAPID_FILIR:
		case MAPID_FILIR_E:
			tmp_item.nameid = 9903;
			break;
		case MAPID_VANILMIRTH:
		case MAPID_VANILMIRTH_E:
			tmp_item.nameid = 9904;
			break;
		case MAPID_EIRA:
			tmp_item.nameid = 9905;
			break;
		case MAPID_BAYERI:
			tmp_item.nameid = 9906;
			break;
		case MAPID_SERA:
			tmp_item.nameid = 9907;
			break;
		case MAPID_DIETER:
			tmp_item.nameid = 9908;
			break;
		case MAPID_ELANOR:
			tmp_item.nameid = 9909;
			break;
		}

		tmp_item.amount = 1;
		tmp_item.identify = 1;
		tmp_item.card[0] = CARD0_CREATE;
		tmp_item.card[1] = hd->homunculus.hom_id;
		tmp_item.card[2] = GetWord(sd->status.char_id, 0); // CharId
		tmp_item.card[3] = GetWord(sd->status.char_id, 1);

		if ((itemflag = pc_additem(sd, &tmp_item, tmp_item.amount, LOG_TYPE_PRODUCE))) {
			clif_additem(sd, 0, 0, itemflag);
			if (battle_config.skill_drop_items_full) {
				map_addflooritem(&tmp_item, tmp_item.amount, sd->bl.m, sd->bl.x, sd->bl.y, 0, 0, 0, 0, 0);
			}
		}

		unit_remove_map(&hd->bl, CLR_OUTSIGHT);
		sd->status.hom_id = 0;
		unit_free(&hd->bl, CLR_OUTSIGHT);
		sd->hd = 0;
		return 1;
	}

	return unit_remove_map(&hd->bl, CLR_OUTSIGHT);
}

 

Getting there, we now need to make a couple changes to skill.cpp:

As with the first version of this process, comment out this line:  (If you already implemented the first version this will be done already)

if (sd->status.hom_id) //Don't delete items when hom is already out.

 

Now find this code block:

case AM_CALLHOMUN:	//[orn]
		if (sd && !hom_call(sd))
			clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
		break;

 

And replace it with this:

case AM_CALLHOMUN:
		if (sd && !sd->status.hom_id) {
			clif_sendembryo(sd);
			clif_skill_nodamage(src, bl, skill_id, skill_lv, 1);
		}
		else if (sd && !hom_call(sd, 0)) {
			clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
		}
		break;

 

Now, find this line in script.cpp:

hom_call(sd); // Respawn homunculus.

 

and replace it with this:

hom_call(sd, 0); // Respawn homunculus.

 

Phew!  Done with code changes!  Now we just need to add the new embryos into the item-db (for a fun challenge, see if you can determine what their item ids are from the code before we proceed further).

 

If you have the old, text based item-db, you need to add this:

9901,Lif_Embryo,Lif Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9902,Amistr_Embryo,Amistr Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9903,Filir_Embryo,Filir Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9904,Vanilmirth_Embryo,Vanilmirth Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9905,Eira_Embryo,Eira Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9906,Bayeri_Embryo,Bayeri Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9907,Sera_Embryo,Sera Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9908,Dieter_Embryo,Dieter Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}
9909,Eleanor_Embryo,Eleanor Embryo,3,10,,10,,,,,,,,,,,,,{},{},{}

 

If you have the new, yaml-based item-db, you need to add this to item-db-etc.yml

  - Id: 9901
    AegisName: Lif_Embryo
    Name: Lif Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9902
    AegisName: Amistr_Embryo
    Name: Amistr Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9903
    AegisName: Filir_Embryo
    Name: Filir Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9904
    AegisName: Vanilmirth_Embryo
    Name: Vanilmirth Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9905
    AegisName: Eira_Embryo
    Name: Eira Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9906
    AegisName: Bayeri_Embryo
    Name: Bayeri Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9907
    AegisName: Sera_Embryo
    Name: Sera Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9908
    AegisName: Dieter_Embryo
    Name: Dieter Embryo
    Type: Etc
    Buy: 10
    Weight: 10
  - Id: 9909
    AegisName: Eleanor_Embryo
    Name: Eleanor Embryo
    Type: Etc
    Buy: 10
    Weight: 10

 

Finally, we just need to add the following to iteminfo.lub:

	[9901] = {
		unidentifiedDisplayName = "Lif Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Lif Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Lif.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9902] = {
		unidentifiedDisplayName = "Amistr Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Amistr Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing an Amistr.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9903] = {
		unidentifiedDisplayName = "Filir Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Filir Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Filir.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9904] = {
		unidentifiedDisplayName = "Vanilmirth Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Vanilmirth Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Vanilmirth.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9905] = {
		unidentifiedDisplayName = "Eira Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Eira Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing an Eira.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9906] = {
		unidentifiedDisplayName = "Bayeri Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Bayeri Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Bayeri.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9907] = {
		unidentifiedDisplayName = "Sera Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Sera Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Sera.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9908] = {
		unidentifiedDisplayName = "Dieter Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Dieter Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing a Dieter.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},
	[9909] = {
		unidentifiedDisplayName = "Eleanor Embryo",
		unidentifiedResourceName = "¿¥ºê¸®¿À",
		unidentifiedDescriptionName = { "..." },
		identifiedDisplayName = "Eleanor Embryo",
		identifiedResourceName = "¿¥ºê¸®¿À",
		identifiedDescriptionName = {
			"An embryo containing an Eleanor.",
			"^FFFFFF_^000000",
			"Weight:^009900 1^000000"
		},
		slotCount = 0,
		ClassNum = 0
	},

 

And that should be everything!  Now just recompile the codebase and restart the server and you should have the new homunculus behaviour.  Hopefully I didn't miss anything in the steps.

Thanks so much for sharing this with everyone. I'll even use it too.
Just to let you know that in skill.cpp this line you asked to look for is different, there are more things.

	case AM_CALLHOMUN:	//[orn]
		if (sd && !hom_call(sd))
			clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
		break;

in lasted rA:
 

	case AM_CALLHOMUN:	//[orn]
		if (sd && !hom_call(sd))
			clif_skill_fail(sd,skill_id,USESKILL_FAIL_LEVEL,0);
#ifdef RENEWAL
		else if (sd && hom_is_active(sd->hd))
			skill_area_temp[0] = 1; // Already passed pre-cast checks
#endif
		break;

It would be good to update your post with the fix.

  • Upvote 2
Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  1
  • Topics Per Day:  0.00
  • Content Count:  41
  • Reputation:   10
  • Joined:  08/22/22
  • Last Seen:  

@Tero Hmm. I implemented it today to test. and I noticed that it is consuming 2 embryo to create 1 homunculus. and the embryo selection window does not close when choosing the embryo.

 

Edited by Pokye
Link to comment
Share on other sites


  • Group:  Members
  • Topic Count:  8
  • Topics Per Day:  0.02
  • Content Count:  54
  • Reputation:   106
  • Joined:  10/01/22
  • Last Seen:  

I did some testing based on your video.  The double deletion of embyros is legitimately a glitch, though it only occurs on the front end, you'll get your embryos back if you relog.  The problem is that in the hom_call method, it was calling clif_delitem after pc_delitem, which it doesn't need to because pc_delitem also calls that method.  The answer is just to remove those two lines of code, which I've done in the source code above for the hom_call method.

 

For the window not closing, I can't reproduce that on my side, even if you trigger the "multi-deletion" glitch above, it still closes.  Closing the window is linked to the "clif_menuskill_clear(sd);" call at the end of "clif_parse_SelectEgg".  This is totally unchanged in latest rathena so I'm not sure what could cause this not to function.  If you do figure it out please post it here because Whiteeagle seems to have the same issue.

 

By the way, as for having multiple embryos of the same type, this is generally intended to be possible, and probably isn't feasibly preventable.  I agree it is kind of confusing to tell which is which if you have several, though as far as I can tell through my testing the window does behave properly, for example if you have 2 Filir Eggs and pick the second one, you do consistently get the one in the second position in your inventory, though of course as the items look identical keeping track of this is difficult.

Edited by Tero
Link to comment
Share on other sites

  • 6 months later...

  • Group:  Members
  • Topic Count:  1
  • Topics Per Day:  0.00
  • Content Count:  3
  • Reputation:   0
  • Joined:  06/08/15
  • Last Seen:  

Has anybody figured out why the window doesn't close? I've tried a million things, but it still doesn't take clif_parse_selectegg. Thanks Tero once again for your great contribution.

Link to comment
Share on other sites

  • 1 month later...

  • Group:  Members
  • Topic Count:  1
  • Topics Per Day:  0.00
  • Content Count:  4
  • Reputation:   0
  • Joined:  09/09/15
  • Last Seen:  

For the double deletion, I removed item cost in `skill_db.yml`, and seems like it works very well (no more double deletion):

image.png.3e426cde977fe0b386b3ea6b3aa0cbeb.png

Besides, I still encounter the windows wasn't closed after selected.

But once again, thank you verymuch Tero, your work is great and the guide is quite easy to follow xD

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...