Sack City Mod Development Thread

stolenrays

Deity
Joined
Aug 2, 2009
Messages
2,063
Sack City Mod v1.2​

This thread is to discuss the Sack City Mod. PIE, Playtyping, and I have been working on it. It features sdk/python/xml changes. I primarliy had added the sdk and a little Python/Xml work. PIE is doing almost all of the python/xml coding currently and Playtyping is concentrating on Python coding/bugs & SDK Bugs. Much of my inspiration came from Afforess's unimplemented Sack feature from ROM:AND.

---Features:---
1. New conquer city option to sack a city.
2. The conqueror gets a special sum of gold :gold:(treasure / number of recent cities)
3. Buildings get destroyed
4. All units in the surroundings of 2 plots get removed(BTS bug to remove)
5. The city gets a hidden info for 10 turns, that it got sacked. so, if you'd sack the city again after some turns, the conqueror will get nothing.
6. Event Messages/Sound effect to yourself/opponent after sacking
7. 2 Conscripts spawn inside the city after sacking.
8. Leaders remember when you Sack their City/Holy City.
9. Custom GameOption.
10. AI implemented

---For Modmakers----
Changes have been commented out as SACK CITY START/END in the SDK

---Version History---
Spoiler :
Version 1.2
-Added gold icon to sack message

-Version 1.1
-Added cannont Sack Islands (From ROM: AND)
-Changed python event text somewhat
-AI decides between Razing & Sacking (From More Naval AI)
-Added Concept Text
-Added Readme & Changelog Text
-Cannot Sack your own cities
-Cannot sack previously owned cities
-cannot sack cities in which you are the highest culture
-Added canSack check


SVN Version LINK--Let me know if you want added to have editing permission.

Download
 

Attachments

  • Civ4ScreenShot0007.JPG
    Civ4ScreenShot0007.JPG
    184.8 KB · Views: 176
  • Civ4ScreenShot0008.JPG
    Civ4ScreenShot0008.JPG
    156.9 KB · Views: 80
I tried it out and noticed some other things:
1) partisan codes are copied over under CityAcquiredAndKept2.
However, it is still using "city" as a variable which is undefined.

2) After sacking the city, the ownership of the city returns to the highest culture owner, but not all units are thrown out. So I can recapture it again in the same turn.

3) As for point number 5, I recheck it.
Code:
 elif pPrePlayer.isHuman():
                      CyInterface().addMessage(iNewOwner, True, 10, CyTranslator().getText("TXT_KEY_MESSAGE_CITY_SACK_4",(pCity.getName(),)), None, 2, None, ColorTypes(8), pCity.getX(), pCity.getY(), True, True)
This code will activate if the previous player is human, yet the message is displayed for the new owner. So you will never see this message

4) Also, as I told you,
Code:
iPreOwner = pCity.findHighestCulture()
you may end up sacking a wrong player
 
Ok, I have updated the mod.

1. the partisan vars are adjusted
2. the messages are adjusted
3. the buildings got a list. that's true, that's more confortable , I forgot to do it like this, thx pla
4. using city.plot
5. inumcities is 1 if zero
6. it's a BTS bug, when you're attacking a city in a stack. I can't reach the unit ID although I go through all of them with plot.getNumUnits
7. If the city has no culture, it goes to the original owner (founder)
It is best to take the highest culture: eg:
Team A belongs to a city which gets conquered by Team B. Team C frees the city and the city will get to Team A again. That's my intention.
The problem is if a city has no culture, but then I take the founder now.

@Stolen: could you send me in onCityAquiredAndKept2 the city owner and the unit ID that conquered the city so I can get them via argslist?

cause: if I got no culture: I'll take this owner and perhaps then I can solve the BTS Bug with the last unit standing in the city. I think it's the unit that conquered the city which is still standing in there, if you attack in a stack.

Edit: removed the old file
 
The problem with getNumUnits and getUnit codes is sometimes it somehow refers to a non-existing unit which I don't know why either.

The non-existing unit has nothing to do with last unit or not. Totally random
You can try placing 100 units on each of the tiles around the city, and see how many are left there after sacking.
Thus, when 1 of them refers to an non-existing unit, then the code will skip 1 unit since it only loops for getNumUnits

Thus, I use while loops instead of for loops for my own codes and so far so good.
Something similar I did with a recent wonder:
Code:
pPlot = pCity.plot()
while pPlot.getNumUnits() > 0:
	pUnit = pPlot.getUnit(0)
	pUnit.jumpToNearestValidPlot()

Some Suggestions for Balance:
1) Limit the Gold to a %, like 20%
Else, in small maps where the player only has 3 or 4 cities, sacking a city can get 33% of his total gold which is alot.

2) Add some defenders back to the city after city.
Since you are still at war after sacking, you can literally take it back in the same turn, since the city has totally no defenders.
Although units nearby are kicked out, 1 unit with Commando Promotion can easily take the empty city
 
Oh yes, the while version did it! great!

a suggestion from me:
when conquering a city, you always get a gold when the popup appears to either keep, sack or destroy the city or sack it...

about your suggestions:
gold: I think it's ok. That's the problem of the defender. But the positive aspect is, that he keeps the city.
putting defenders into the city again: this is
1 unrealistic
2 you can't get money if you sack the city within 10 rounds, and you always will be able to sack a city twice (road promotion)
3 the units will be hard to find: does a city always has a value in getConscriptUnit? if yes, then it's no problem (otherwise it is mod based, which units are available...)

stolenrays, what do you say about platyping's suggestions?
 
Code:
for i in range(2):
	pNewUnit = pCulturePlayer.initUnit(pCity.getConscriptUnit(),iX,iY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)
	pCity.addProductionExperience(pNewUnit, true)

Again from the same recent Wonder, adds 2 conscripts with conscript experience after transfer of city ownership.

The thing is, if you don't add any defenders back to the city, you can literally re-capture the city back in the same turn.
So you sack it to get the gold, and take back the city directly again, this time to keep it for good.

In default BTS, the first conscriptable unit is Warrior, so there will always be a conscript unit.
 
Oh yes, that's true! Ok I add those units!

Here's the actual file

@stolen: now it's a mod of platyping, you and me! ;)
 
a suggestion from me:
when conquering a city, you always get a gold when the popup appears to either keep, sack or destroy the city or sack it...

By the way, I don't understand this part.
Yes, I know you always get some gold when you capture the city, before you decide what you do with the city.
But what is your suggestion?
Remove it? If so, this has been done by Tsentom's Himeiji Castle I believe
 
Adds two conscripts is very important. The city is unguarded if you keep the war and actually you could plunder and conquer in the same turn.

What about AI? What are the conditions him to decide what to do with the city?
 
Yes, this is what I mean. You get gold for conquering and afterwars a gold for sacking. But ok, it's not so unrealistic. We can keep it.

Cruel: oh, the AI! yes, this is stolenrays part, he has to mention it in SDK to which function the AI gets linked to.
 
I'm real busy at school this week, but I'll try to chime in as much as I can. Maybe the gold received can be dependant on the buildings destroyed somehow? If that is too difficult, then that is OK. Then, you wouldn't be taking too much gold from anyone and it could simulate how monuments & treasuries were often pillaged.
 
just edit the doCityCaptureGold part in CvGameUtils and let it return 0.
Then the initial pillage gold will always be 0
 
I'm real busy at school this week, but I'll try to chime in as much as I can. Maybe the gold received can be dependant on the buildings destroyed somehow? If that is too difficult, then that is OK. Then, you wouldn't be taking too much gold from anyone and it could simulate how monuments & treasuries were often pillaged.

that's no problem! ;) I'll do it as soon I have the time for that.

just edit the doCityCaptureGold part in CvGameUtils and let it return 0.
Then the initial pillage gold will always be 0

Ah ok. But I would keep it as it is. The one who wants more gold, sacks the city :hammer:
 
Ok, I have updated the mod.

1. @Stolen: could you send me in onCityAquiredAndKept2 the city owner and the unit ID that conquered the city so I can get them via argslist?

cause: if I got no culture: I'll take this owner and perhaps then I can solve the BTS Bug with the last unit standing in the city. I think it's the unit that conquered the city which is still standing in there, if you attack in a stack.

I'm not sure I know what exactly do you want me to send you/change? Something from the sdk?

I've never edited the AI, except to mention things. Maybe I could get some tips. I will look at it and see if I can make something up.
 
Code:
while iNumDestroyed < iNumDestroyBuildings:
     for i in CityBuildings:
        if iNumDestroyed >= iNumDestroyBuildings: break
        if CyGame().getSorenRandNum(2, "Sack: Random building gets destroyed") == 1:
             pCity.setNumRealBuilding(i,0)
             iNumDestroyed += 1

One thing I noticed:
Every iteration of the while loop, you are looping through the list of citybuildings and then destroy the building if the random number is 1.

However, if you destroy a granary in the first iteration of the while loop, during the next iteration, you are still going through the same list of citybuildings. Thus, you may end up destroying granary again, and increasing the number of buildings destroyed by 1. Conclusion, the actual number of buildings destroyed may be less than the expected number due to certain buildings destroyed multiple times.

Solution:
1) Remove the destroyed buildings from the list for next iterations.
2) Check if building still present before rolling the random number and destroying.
Either 1 or 2 is enough
 
I don't know about the conscripts in the city. I figure have the partisans would be good enough, or some extra in the city. I really don't have an opinion on the gold before/after sacking dilemma.

I went through the sdk and found what I think is the function I need to add to to make the AI use this Mod:

Spoiler :
Code:
void CvPlayerAI::AI_conquerCity(CvCity* pCity)
{
	CvCity* pNearestCity;
	bool bRaze = false;
	int iRazeValue;
	int iI;

	if (canRaze(pCity))
	{
	    iRazeValue = 0;
		if (GC.getGameINLINE().getElapsedGameTurns() > 20)
		{
			if (getNumCities() > 4)
			{
				if (!(pCity->isHolyCity()) && !(pCity->hasActiveWorldWonder()))
				{
					if (pCity->getPreviousOwner() != BARBARIAN_PLAYER)
					{
						pNearestCity = GC.getMapINLINE().findCity(pCity->getX_INLINE(), pCity->getY_INLINE(), NO_PLAYER, getTeam(), true, false, NO_TEAM, NO_DIRECTION, pCity);

						if (pNearestCity == NULL)
						{
							if (pCity->getPreviousOwner() != NO_PLAYER)
							{
								if (GET_TEAM(GET_PLAYER(pCity->getPreviousOwner()).getTeam()).countNumCitiesByArea(pCity->area()) > 3)
								{
									iRazeValue += 30;
								}
							}
						}
						else
						{
							int iDistance = plotDistance(pCity->getX_INLINE(), pCity->getY_INLINE(), pNearestCity->getX_INLINE(), pNearestCity->getY_INLINE());
							if ( iDistance > 12)
							{
								iRazeValue += iDistance * 2;
							}
						}
					}


					if (pCity->area()->getCitiesPerPlayer(getID()) > 0)
					{
						if (AI_isFinancialTrouble())
						{
							iRazeValue += (70 - 15 * pCity->getPopulation());
						}
					}
                
			        if (getStateReligion() != NO_RELIGION)
					{
                        if (pCity->isHasReligion(getStateReligion()))
                        {
							if (GET_TEAM(getTeam()).hasShrine(getStateReligion()))
							{
	                        iRazeValue -= 50;                            
						}
							else
							{
								iRazeValue -= 10;
							}
						}
					}
                    
                    int iCloseness = pCity->AI_playerCloseness(getID());
                    if (iCloseness > 0)
                    {
                    	iRazeValue -= 25;
                    	iRazeValue -= iCloseness * 2;
					}
                    else
                    {
                    	iRazeValue += 60;
					}

					if (pCity->area()->getCitiesPerPlayer(getID()) > 0)
					{
						if (pCity->getPreviousOwner() != BARBARIAN_PLAYER)
						{
                            iRazeValue += GC.getLeaderHeadInfo(getPersonalityType()).getRazeCityProb();
						}
					}
					
					if (iRazeValue > 0)
					{
					    for (iI = 0; iI < GC.getNumTraitInfos(); iI++)
					    {
                            if (hasTrait((TraitTypes)iI))
                            {
                                iRazeValue *= (100 - (GC.getTraitInfo((TraitTypes)iI).getUpkeepModifier()));
                                iRazeValue /= 100;
							}
						}

                        if (GC.getGameINLINE().getSorenRandNum(100, "AI Raze City") < iRazeValue)
						{
							bRaze = true;
							pCity->doTask(TASK_RAZE);
						}
					}
				}
			}
		}
	}

	if (!bRaze)
	{
		CvEventReporter::getInstance().cityAcquiredAndKept(GC.getGameINLINE().getActivePlayer(), pCity);
	}
}

I'm gonna delve deeper into this, but I think I could just replicate most of this and then just sub in the new sdk definition of the Sack Mod? I also fixed and added the commenting for the sdk
 
1) Partisan will only trigger with emancipation civics.
2) Partisan may spawn units in capital instead of the city, leaving the city empty.
3) Partisan place units randomly around the city, but the city is still empty

Conclusion, the city is still empty
 
I think I am getting old :D
thx pla, you are a very good programmer that you can see those consequences of loops.

I did it in a version following to your 1. note: I catch a random number from the list and remove its item:
PHP:
                      while iNumDestroyed < iNumDestroyBuildings:
                        iRand = CyGame().getSorenRandNum(len(CityBuildings), "Sack: Random building gets destroyed")
                        pCity.setNumRealBuilding(CityBuildings[iRand],0)
                        CityBuildings.remove(iRand)
                        iNumDestroyed += 1

I now calculate the gold bonus with the number of destroyed buildings, regardless of the kind of buildings.

So small and new cities will give 0 pieces of gold! ;)
PHP:
                    iTreasure  = pPrePlayer.getGold()
                    iNumCities = pPrePlayer.getNumCities()

                    if iNumCities == 0: iNumCities = 1
                    iGold = iTreasure / iNumCities

                    # In dependence of destroyed buildings
                    if iNumCityBuildings * iNumDestroyBuildings < 1: iGold = 0
                    else: iGold = iGold / iNumCityBuildings * iNumDestroyBuildings

ah, stolen, no I don't need new arguments from the SDK, with the previousowner-function I should get the last owner and with the while getnumunits-trick of pla, I don't need the UnitID of the conquering unit. so, everything's ok now.

Any suggestions?
 
I like to read through my own codes every few weeks to catch logic errors as well :D
Logic errors are hardest to catch since the computer is not going to tell you about it.

I did ask stolen before whether it would be better if the whole codes are done under def onCityAcquired instead.
Since def onCityAcquired already have iPreviousOwner so it will give a correct previous owner instead of the highest culture player.
But since you mentioned that you do want the culture player rather than previous owner I forget about this.

One question though, I assume that def onCityAcquiredAndKept will also be triggered due to trade, liberation? If so, does that mean when someone give me a city, I can sack it?
If yes, then you may want to use onCityAcquired instead since there is a bConquest usable to check.

Another thing, the sack portion onCityDoTurn, the throw units out is still using the for loop style.
But why is this part needed? Does it mean after I sack the city, during the 10 turns, if I move and end my units around the city, I will see my units get thrown out again?
 
k ;)

oh damn, yes I forgot that part, this can get out. (for loop in oncitydoturn). Just changed it, and this time, I downloaded 0.4.7 before :D

about onCityAcquiredAndKept: oh that's true: stolen: can you disable the button in SDK, when bConquest ist false?
 
Back
Top Bottom