فهرست منبع

- CMappedFileLoader class to remap WoG files to h3 names
- WoG should be optional, all remapped files are listed in WoG/config/wogFileOverrides.json
- fixed several cases of incorrect positioning of creatures in battles
- some missing sounds for battle effects
- negative luck support, disabled by default
- a bit hackish detection of WoG presence, VCMI should work on SoD-only installs

Ivan Savenko 12 سال پیش
والد
کامیت
8be801a6dc

+ 3 - 2
CMakeLists.txt

@@ -140,12 +140,13 @@ endif()
 
 # For apple this files will be already inside vcmiclient bundle
 if (NOT APPLE)
-	# copy whole directory but .svn control files and user-specific settings.json
+	# copy whole directory but .svn control files
 	install(DIRECTORY config DESTINATION ${DATA_DIR} PATTERN ".svn" EXCLUDE)
 	# copy vcmi mod along with all its content
 	install(DIRECTORY Mods/vcmi DESTINATION ${DATA_DIR}/Mods PATTERN ".svn" EXCLUDE)
-	# copy only mod.json for WoG
+	# copy only files added by vcmi for WoG
 	install(FILES Mods/WoG/mod.json DESTINATION ${DATA_DIR}/Mods/WoG)
+	install(DIRECTORY Mods/WoG/config DESTINATION ${DATA_DIR}/Mods/WoG PATTERN ".svn" EXCLUDE)
 
 	install(FILES vcmibuilder DESTINATION ${BIN_DIR} PERMISSIONS
 		OWNER_WRITE OWNER_READ OWNER_EXECUTE

+ 20 - 0
Mods/WoG/config/defaultMods.json

@@ -0,0 +1,20 @@
+// default configuration for mod system loaded at launch
+
+{
+	"textData" :
+	{
+		"heroClass"  : 18,
+		"artifact"   : 171,
+		"creature"   : 197,
+		"faction"    : 9,
+		"hero"       : 156
+	},
+
+	"modules":
+	{
+		"STACK_EXPERIENCE": true,
+		"STACK_ARTIFACTS": true,
+		"COMMANDERS": true,
+		"MITHRIL": false //so far unused
+	}
+}

+ 45 - 0
Mods/WoG/config/wogFileOverrides.json

@@ -0,0 +1,45 @@
+{
+	// Text configs
+	"data/crgen1.txt"   : "data/zcrgn1.txt",
+	"data/crtraits.txt" : "data/zcrtrait.txt",
+	"data/help.txt"     : "data/zelp.txt",
+	"data/objects.txt"  : "data/zobjcts.txt",
+
+	// main menu images
+	"data/gamselb0.bmp" : "data/ZPIC1000.bmp", // map selection screen
+	"data/gamselb1.bmp" : "data/ZPIC1001.bmp", // map selection screen
+	"data/loadbar.bmp"  : "data/ZPIC106.bmp",  // loading screen
+	"data/gamselbk.bmp" : "data/ZPIC1005.bmp", // background
+	"data/newgame.bmp"  : "data/ZNEWGAM.bmp",  // "new  game" text
+	"data/loadgame.bmp" : "data/ZLOADGAM.bmp", // "load game" text
+
+	// main menu buttons
+	"sprites/mmenung.def" : "sprites/zmenung.def",
+	"sprites/mmenulg.def" : "sprites/zmenulg.def",
+	"sprites/mmenuhs.def" : "sprites/zmenuhs.def",
+	"sprites/mmenucr.def" : "sprites/zmenucr.def",
+	"sprites/mmenuqt.def" : "sprites/zmenuqt.def",
+
+	// game type select (single/multi/campaign)
+	"sprites/gtsingl.def" : "sprites/ztsingl.def",
+	"sprites/gtmulti.def" : "sprites/ztmulti.def",
+	"sprites/gtcampn.def" : "sprites/ztcampn.def",
+	"sprites/gttutor.def" : "sprites/zttutor.def",
+	"sprites/gtback.def"  : "sprites/ztback.def",
+
+	// campaigns
+	"sprites/csssod.def" : "sprites/zsssod.def",
+	"sprites/cssroe.def" : "sprites/zssroe.def",
+	"sprites/cssarm.def" : "sprites/zssarm.def",
+	"sprites/csscus.def" : "sprites/zsscus.def",
+
+	// resource bars
+	"data/tresbar.bmp" : "data/zresbar.bmp",
+	"data/kresbar.bmp" : "data/z2esbar.bmp",
+	"data/aresbar.bmp" : "data/zresbar.bmp",
+
+	// misc
+	"data/tpcainfo.bmp" : "data/zpcainfo.bmp", // stats images for town fort
+	"music/mainmenu.mp3" : "music/mainmenuwog.mp3",
+	"video/credits.bik" : "video/acredit.bik"
+}

+ 4 - 0
Mods/WoG/mod.json

@@ -1,6 +1,10 @@
 {
 	"filesystem":
 	{
+		"" :
+		[
+			{ "type" : "map", "path" : "/Config/wogFileOverrides.json"}
+		],
 		"CONFIG/" :
 		[
 			{ "type" : "dir", "path" : "/Config"}

+ 1 - 6
Mods/vcmi/mod.json

@@ -16,10 +16,5 @@
 	},
 
 	"name" : "VCMI essential files",
-	"description" : "Essential files required for VCMI to run correctly",
-	
-	"depends" :
-	[
-		"wog"
-	]
+	"description" : "Essential files required for VCMI to run correctly"
 }

+ 2 - 2
client/CCastleInterface.cpp

@@ -874,7 +874,7 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
 
 	Rect barRect(9, 182, 732, 18);
 	statusbar = new CGStatusBar(new CPicture(*panel, barRect, 9, 555, false));
-	resdatabar = new CResDataBar("ZRESBAR", 3, 575, 32, 2, 85, 85);
+	resdatabar = new CResDataBar("ARESBAR", 3, 575, 32, 2, 85, 85);
 
 	townlist = new CTownList(3, Point(744, 414), "IAM014", "IAM015");
 	if (from)
@@ -1518,7 +1518,7 @@ CFortScreen::RecruitArea::RecruitArea(int posX, int posY, const CGTownInstance *
 	if (!town->creatures[level].second.empty())
 		addUsedEvents(LCLICK | RCLICK | HOVER);//Activate only if dwelling is present
 	
-	icons = new CPicture("ZPCAINFO", 261, 3);
+	icons = new CPicture("TPCAINFO", 261, 3);
 	buildingPic = new CAnimImage(town->town->clientInfo.buildingsIcons, buildingID, 0, 4, 21);
 
 	const CCreature* creature = NULL;

+ 0 - 1
client/CMT.cpp

@@ -282,7 +282,6 @@ int main(int argc, char** argv)
 	};
 
 	if (!testFile("DATA/HELP.TXT", "Heroes III data") &&
-	    !testFile("DATA/ZELP.TXT", "In the Wake of Gods data") &&
 	    !testFile("MODS/VCMI/MOD.JSON", "VCMI mod") &&
 	    !testFile("DATA/StackQueueBgBig.PCX", "VCMI data"))
 		exit(1); // These are unrecoverable errors

+ 11 - 1
client/CPlayerInterface.cpp

@@ -879,8 +879,17 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba)
 		boost::algorithm::replace_first(hlp,"%s", (stack->count != 1) ? stack->getCreature()->namePl.c_str() : stack->getCreature()->nameSing.c_str());
 		battleInt->console->addText(hlp);
 		battleInt->displayEffect(18, stack->position);
+		CCS->soundh->playSound(soundBase::GOODLUCK);
+	}
+	if(ba->unlucky()) //unlucky hit
+	{
+		const CStack *stack = cb->battleGetStackByID(ba->stackAttacking);
+		std::string hlp = CGI->generaltexth->allTexts[44];
+		boost::algorithm::replace_first(hlp,"%s", (stack->count != 1) ? stack->getCreature()->namePl.c_str() : stack->getCreature()->nameSing.c_str());
+		battleInt->console->addText(hlp);
+		battleInt->displayEffect(48, stack->position);
+		CCS->soundh->playSound(soundBase::BADLUCK);
 	}
-	//TODO: bad luck?
 	if (ba->deathBlow())
 	{
 		const CStack *stack = cb->battleGetStackByID(ba->stackAttacking);
@@ -892,6 +901,7 @@ void CPlayerInterface::battleAttack(const BattleAttack *ba)
 			const CStack * attacked = cb->battleGetStackByID(i->stackAttacked);
 			battleInt->displayEffect(73, attacked->position);
 		}
+		CCS->soundh->playSound(soundBase::deathBlow);
 
 	}
 

+ 1 - 1
client/CSoundBase.h

@@ -213,7 +213,7 @@ VCMI_SOUND_NAME(cyclopMove) VCMI_SOUND_FILE(CYCLMOVE.wav) \
 VCMI_SOUND_NAME(cyclopShot) VCMI_SOUND_FILE(CYCLSHOT.wav) \
 VCMI_SOUND_NAME(cyclopWNCE) VCMI_SOUND_FILE(CYCLWNCE.wav) \
 VCMI_SOUND_NAME(DANGER) VCMI_SOUND_FILE(DANGER.wav) \
-VCMI_SOUND_NAME(deathBlood) VCMI_SOUND_FILE(DEATHBLO.wav) \
+VCMI_SOUND_NAME(deathBlow) VCMI_SOUND_FILE(DEATHBLO.wav) \
 VCMI_SOUND_NAME(deathCloud) VCMI_SOUND_FILE(DEATHCLD.wav) \
 VCMI_SOUND_NAME(deathRIP) VCMI_SOUND_FILE(DEATHRIP.wav) \
 VCMI_SOUND_NAME(deathSTR) VCMI_SOUND_FILE(DEATHSTR.wav) \

+ 5 - 5
client/GUIClasses.cpp

@@ -556,8 +556,8 @@ void CGarrisonInt::createSlots()
 
 void CGarrisonInt::recreateSlots()
 {
-	setSplittingMode(false);
 	selectSlot(nullptr);
+	setSplittingMode(false);
 
 	for(size_t i = 0; i<splitButtons.size(); i++)
 		splitButtons[i]->block(true);
@@ -629,10 +629,10 @@ void CGarrisonInt::setSplittingMode(bool on)
 	if (inSplittingMode || on)
 	{
 		BOOST_FOREACH(CGarrisonSlot * slot, slotsUp)
-			slot->setHighlight(slot->creature == nullptr || slot->creature == getSelection()->creature);
+			slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature)));
 
 		BOOST_FOREACH(CGarrisonSlot * slot, slotsDown)
-			slot->setHighlight(slot->creature == nullptr || slot->creature == getSelection()->creature);
+			slot->setHighlight( ( on && (slot->creature == nullptr || slot->creature == getSelection()->creature)));
 		inSplittingMode = on;
 	}
 }
@@ -1773,7 +1773,7 @@ void CMinorResDataBar::showAll(SDL_Surface * to)
 
 CMinorResDataBar::CMinorResDataBar()
 {
-	bg = BitmapHandler::loadBitmap("Z2ESBAR.bmp");
+	bg = BitmapHandler::loadBitmap("KRESBAR.bmp");
 	SDL_SetColorKey(bg,SDL_SRCCOLORKEY,SDL_MapRGB(bg->format,0,255,255));
 	graphics->blueToPlayersAdv(bg,LOCPLINT->playerID);
 	pos.x = 7;
@@ -5241,7 +5241,7 @@ CPuzzleWindow::CPuzzleWindow(const int3 &GrailPos, double discoveredRatio):
 
 	new CPicture("PUZZLOGO", 607, 3);
 	new CLabel(700, 95, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[463]);
-	new CResDataBar("ZRESBAR.bmp", 3, 575, 32, 2, 85, 85);
+	new CResDataBar("ARESBAR.bmp", 3, 575, 32, 2, 85, 85);
 
 	int faction = LOCPLINT->cb->getStartInfo()->playerInfos.find(LOCPLINT->playerID)->second.castle;
 

+ 15 - 8
client/battle/CBattleAnimations.cpp

@@ -492,7 +492,7 @@ void CMovementAnimation::endAnim()
 {
 	const CStack * movedStack = stack;
 
-	myAnim()->pos = CClickableHex::getXYUnitAnim(nextHex, movedStack->attackerOwned, movedStack, owner);
+	myAnim()->pos = CClickableHex::getXYUnitAnim(nextHex, owner->creDir[stack->ID], movedStack, owner);
 	CBattleAnimation::endAnim();
 
 	if(movedStack)
@@ -604,6 +604,8 @@ CReverseAnimation::CReverseAnimation(CBattleInterface * _owner, const CStack * s
 
 bool CReverseAnimation::init()
 {
+	logGlobal->errorStream() << "Pos at " << myAnim()->pos.x << "x" << myAnim()->pos.y;
+
 	if(myAnim() == NULL || myAnim()->getType() == CCreatureAnim::DEATH)
 	{
 		endAnim();
@@ -614,8 +616,10 @@ bool CReverseAnimation::init()
 	if(!priority && !isEarliest(false))
 		return false;
 
-	if(myAnim()->framesInGroup(CCreatureAnim::TURN_R))
-		myAnim()->setType(CCreatureAnim::TURN_R);
+	//myAnim()->pos = CClickableHex::getXYUnitAnim(hex, owner->creDir[stack->ID], stack, owner);
+
+	if(myAnim()->framesInGroup(CCreatureAnim::TURN_L))
+		myAnim()->setType(CCreatureAnim::TURN_L);
 	else
 		setupSecondPart();
 
@@ -647,6 +651,8 @@ void CReverseAnimation::nextFrame()
 
 void CReverseAnimation::endAnim()
 {
+	logGlobal->errorStream() << "Pos on end " << myAnim()->pos.x << "x" << myAnim()->pos.y;
+
 	CBattleAnimation::endAnim();
 	if( stack->alive() )//don't do that if stack is dead
 		myAnim()->setType(CCreatureAnim::HOLDING);
@@ -656,6 +662,7 @@ void CReverseAnimation::endAnim()
 
 void CReverseAnimation::setupSecondPart()
 {
+	logGlobal->errorStream() << "Pos before 2nd " << myAnim()->pos.x << "x" << myAnim()->pos.y;
 	if(!stack)
 	{
 		endAnim();
@@ -664,9 +671,7 @@ void CReverseAnimation::setupSecondPart()
 
 	owner->creDir[stack->ID] = !owner->creDir[stack->ID];
 
-	Point coords = CClickableHex::getXYUnitAnim(hex, owner->creDir[stack->ID], stack, owner);
-	myAnim()->pos.x = coords.x;
-	//creAnims[stackID]->pos.y = coords.second;
+	myAnim()->pos = CClickableHex::getXYUnitAnim(hex, owner->creDir[stack->ID], stack, owner);
 
 	if(stack->doubleWide())
 	{
@@ -682,10 +687,12 @@ void CReverseAnimation::setupSecondPart()
 		}
 	}
 
+	logGlobal->errorStream() << "Pos after 2nd " << myAnim()->pos.x << "x" << myAnim()->pos.y;
+
 	secondPartSetup = true;
 
-	if(myAnim()->framesInGroup(CCreatureAnim::TURN_L))
-		myAnim()->setType(CCreatureAnim::TURN_L);
+	if(myAnim()->framesInGroup(CCreatureAnim::TURN_R))
+		myAnim()->setType(CCreatureAnim::TURN_R);
 	else
 		endAnim();
 }

+ 2 - 0
client/battle/CBattleInterface.cpp

@@ -1986,6 +1986,7 @@ void CBattleInterface::battleTriggerEffect(const BattleTriggerEffect & bte)
 			std::string hlp = CGI->generaltexth->allTexts[33];
 			boost::algorithm::replace_first(hlp,"%s",(stack->getName()));
 			displayEffect(20,stack->position);
+			CCS->soundh->playSound(soundBase::GOODMRLE);
 			console->addText(hlp);
 			break;
 		}
@@ -2640,6 +2641,7 @@ void CBattleInterface::startAction(const BattleAction* action)
 	case Battle::BAD_MORALE:
 		txtid = -34; //negative -> no separate singular/plural form
 		displayEffect(30,stack->position);
+		CCS->soundh->playSound(soundBase::BADMRLE);
 		break;
 	}
 

+ 1 - 1
config/artifacts.json

@@ -2,7 +2,7 @@
 	"spellBook":
 	{
 		"index" : 0,
-		"type" : ["HERO"]
+		"type" : ["HERO"],
 	},
 	"spellScroll":
 	{

+ 1 - 1
config/creatures/inferno.json

@@ -398,7 +398,7 @@
 			"hateArchAngels" : 
 			{
 				"type" : "HATE",
-				"subtype" : "creature.angel",
+				"subtype" : "creature.archangel",
 				"val" : 50
 			},
 			"FLYING_ARMY" :

+ 7 - 6
config/defaultMods.json

@@ -4,8 +4,8 @@
 	"textData" :
 	{
 		"heroClass"  : 18,
-		"artifact"   : 171,
-		"creature"   : 197,
+		"artifact"   : 144,
+		"creature"   : 150,
 		"faction"    : 9,
 		"hero"       : 156
 	},
@@ -17,13 +17,14 @@
 		"NEUTRAL_STACK_EXP_DAILY" : 500,
 		"MAX_BUILDING_PER_TURN" : 1,
 		"DWELLINGS_ACCUMULATE_CREATURES" : true,
-		"ALL_CREATURES_GET_DOUBLE_MONTHS" : false
+		"ALL_CREATURES_GET_DOUBLE_MONTHS" : false,
+		"NEGATIVE_LUCK" : false
 	},
 	"modules":
 	{
-		"STACK_EXPERIENCE": true,
-		"STACK_ARTIFACTS": true,
-		"COMMANDERS": true,
+		"STACK_EXPERIENCE": false,
+		"STACK_ARTIFACTS": false,
+		"COMMANDERS": false,
 		"MITHRIL": false //so far unused
 	}
 }

+ 27 - 27
config/mainmenu.json

@@ -1,15 +1,15 @@
 {
 	//images used in game selection screen
-	"game-select" : ["ZPIC1000", "ZPIC1001"],
+	"game-select" : ["gamselb0", "gamselb1"],
 
-	"loading" : ["ZPIC106", "LoadBar"],
+	"loading" : ["loadbar"],
 
 	//Main menu window, consists of several sub-menus aka items
 	"window":
 	{
-		"background" : "ZPIC1005",
+		"background" : "gamselbk",
 		//"scalable" : true, //background will be scaled to screen size
-		"video" :    {"x": 8, "y": 105, "name":"ACREDIT.SMK" },//Floating WoG logo
+		"video" :    {"x": 8, "y": 105, "name":"CREDITS.SMK" },//Floating WoG logo
 		//"images" : [],//Optioal, contains any additional images in the same format as video
 		"items" : 
 		[
@@ -17,46 +17,46 @@
 				"name" : "main",
 				"buttons":
 				[
-					{"x": 540, "y": 10,  "name":"ZMENUNG", "hotkey" : 110, "help": 3, "command": "to new"},
-					{"x": 532, "y": 132, "name":"ZMENULG", "hotkey" : 108, "help": 4, "command": "to load"},
-					{"x": 524, "y": 251, "name":"ZMENUHS", "hotkey" : 104, "help": 5, "command": "highscores"},
-					{"x": 557, "y": 359, "name":"ZMENUCR", "hotkey" : 99,  "help": 6, "command": "to credits"},
-					{"x": 586, "y": 468, "name":"ZMENUQT", "hotkey" : 27,  "help": 7, "command": "exit"}
+					{"x": 540, "y": 10,  "name":"MMENUNG", "hotkey" : 110, "help": 3, "command": "to new"},
+					{"x": 532, "y": 132, "name":"MMENULG", "hotkey" : 108, "help": 4, "command": "to load"},
+					{"x": 524, "y": 251, "name":"MMENUHS", "hotkey" : 104, "help": 5, "command": "highscores"},
+					{"x": 557, "y": 359, "name":"MMENUCR", "hotkey" : 99,  "help": 6, "command": "to credits"},
+					{"x": 586, "y": 468, "name":"MMENUQT", "hotkey" : 27,  "help": 7, "command": "exit"}
 				]
 			},
 			{
 				"name" : "new",
 				"buttons":
 				[
-					{"x": 545, "y": 4,   "name":"ZTSINGL", "hotkey" : 115, "help": 10, "command": "start single"},
-					{"x": 568, "y": 120, "name":"ZTMULTI", "hotkey" : 109, "help": 12, "command": "start multi"},
-					{"x": 541, "y": 233, "name":"ZTCAMPN", "hotkey" : 99,  "help": 11, "command": "to campaign"},
-					{"x": 545, "y": 358, "name":"ZTTUTOR", "hotkey" : 116, "help": 13, "command": "start tutorial"},
-					{"x": 582, "y": 464, "name":"ZTBACK",  "hotkey" : 27,  "help": 14, "command": "to main"}
+					{"x": 545, "y": 4,   "name":"GTSINGL", "hotkey" : 115, "help": 10, "command": "start single"},
+					{"x": 568, "y": 120, "name":"GTMULTI", "hotkey" : 109, "help": 12, "command": "start multi"},
+					{"x": 541, "y": 233, "name":"GTCAMPN", "hotkey" : 99,  "help": 11, "command": "to campaign"},
+					{"x": 545, "y": 358, "name":"GTTUTOR", "hotkey" : 116, "help": 13, "command": "start tutorial"},
+					{"x": 582, "y": 464, "name":"GTBACK",  "hotkey" : 27,  "help": 14, "command": "to main"}
 				],
-				"images": [ {"x": 114, "y": 312, "name":"ZNEWGAM"} ]
+				"images": [ {"x": 114, "y": 312, "name":"NEWGAME"} ]
 			},
 			{
 				"name" : "load",
 				"buttons":
 				[
-					{"x": 545, "y": 8,   "name":"ZTSINGL", "hotkey" : 115, "help": 10, "command": "load single"},
-					{"x": 568, "y": 120, "name":"ZTMULTI", "hotkey" : 109, "help": 12, "command": "load multi"},
-					{"x": 541, "y": 233, "name":"ZTCAMPN", "hotkey" : 99,  "help": 11, "command": "load campaign"},
-					{"x": 545, "y": 358, "name":"ZTTUTOR", "hotkey" : 116, "help": 13, "command": "load tutorial"},
-					{"x": 582, "y": 464, "name":"ZTBACK",  "hotkey" : 27,  "help": 14, "command": "to main"}
+					{"x": 545, "y": 8,   "name":"GTSINGL", "hotkey" : 115, "help": 10, "command": "load single"},
+					{"x": 568, "y": 120, "name":"GTMULTI", "hotkey" : 109, "help": 12, "command": "load multi"},
+					{"x": 541, "y": 233, "name":"GTCAMPN", "hotkey" : 99,  "help": 11, "command": "load campaign"},
+					{"x": 545, "y": 358, "name":"GTTUTOR", "hotkey" : 116, "help": 13, "command": "load tutorial"},
+					{"x": 582, "y": 464, "name":"GTBACK",  "hotkey" : 27,  "help": 14, "command": "to main"}
 				],
-				"images": [ {"x": 114, "y": 312, "name":"ZLOADGAM"} ]
+				"images": [ {"x": 114, "y": 312, "name":"LOADGAME"} ]
 			},
 			{
 				"name" : "campaign",
 				"buttons":
 				[
-					{"x": 535, "y": 4,   "name":"ZSSSOD", "hotkey" : 119, "command": "campaigns sod"},
-					{"x": 494, "y": 117, "name":"ZSSROE", "hotkey" : 114, "command": "campaigns roe"},
-					{"x": 486, "y": 241, "name":"ZSSARM", "hotkey" : 97,  "command": "campaigns ab"},
-					{"x": 550, "y": 358, "name":"ZSSCUS", "hotkey" : 99,  "command": "start campaign"},
-					{"x": 582, "y": 464, "name":"ZTBACK", "hotkey" : 27,  "command": "to new"}
+					{"x": 535, "y": 4,   "name":"CSSSOD", "hotkey" : 119, "command": "campaigns sod"},
+					{"x": 494, "y": 117, "name":"CSSROE", "hotkey" : 114, "command": "campaigns roe"},
+					{"x": 486, "y": 241, "name":"CSSARM", "hotkey" : 97,  "command": "campaigns ab"},
+					{"x": 550, "y": 358, "name":"CSSCUS", "hotkey" : 99,  "command": "start campaign"},
+					{"x": 582, "y": 464, "name":"GTBACK", "hotkey" : 27,  "command": "to new"}
 				],
 			}
 		]
@@ -116,7 +116,7 @@
 
 		},
 		{
-			"name":"wog",
+			"name":"wog", /// wog campaigns, currently has no assigned button in campaign screen and thus unused
 			"images" : [ {"x": 0, "y": 0, "name":"CAMPZALL"} ],
 			"exitbutton" : {"x": 658, "y": 482, "name":"CMPSCAN", "hotkey" : 27},
 			"items":

+ 1 - 1
config/schemas/mod.json

@@ -69,7 +69,7 @@
 						},
 						"type": {
 							"type" : "string",
-							"enum" : [ "dir", "lod", "snd", "vid" ],
+							"enum" : [ "dir", "lod", "snd", "vid", "map" ],
 							"description" : "Type of data source"
 						}
 					}

+ 3 - 3
lib/BattleState.cpp

@@ -89,9 +89,9 @@ std::pair< std::vector<BattleHex>, int > BattleInfo::getPath(BattleHex start, Ba
 }
 
 ui32 BattleInfo::calculateDmg( const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero,
-	bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg )
+	bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg )
 {
-	TDmgRange range = calculateDmgRange(attacker, defender, shooting, charge, lucky, deathBlow, ballistaDoubleDmg);
+	TDmgRange range = calculateDmgRange(attacker, defender, shooting, charge, lucky, unlucky, deathBlow, ballistaDoubleDmg);
 
 	if(range.first != range.second)
 	{
@@ -636,7 +636,7 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
 		}
 	case BFieldType::CLOVER_FIELD:
 		{ //+2 luck bonus for neutral creatures
-			curB->addNewBonus(makeFeature(Bonus::LUCK, Bonus::ONE_BATTLE, 0, +2, Bonus::TERRAIN_OVERLAY)->addLimiter(make_shared<CreatureFactionLimiter>(-1)));
+			curB->addNewBonus(makeFeature(Bonus::LUCK, Bonus::ONE_BATTLE, 0, +2, Bonus::TERRAIN_OVERLAY)->addLimiter(make_shared<CreatureAlignmentLimiter>(EAlignment::NEUTRAL)));
 			break;
 		}
 	case BFieldType::EVIL_FOG:

+ 1 - 1
lib/BattleState.h

@@ -97,7 +97,7 @@ struct DLL_LINKAGE BattleInfo : public CBonusSystemNode, public CBattleInfoCallb
 	shared_ptr<CObstacleInstance> getObstacleOnTile(BattleHex tile) const;
 	std::set<BattleHex> getStoppers(bool whichSidePerspective) const;
 
-	ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting)
+	ui32 calculateDmg(const CStack* attacker, const CStack* defender, const CGHeroInstance * attackerHero, const CGHeroInstance * defendingHero, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg); //charge - number of hexes travelled before attack (for champion's jousting)
 	void calculateCasualties(std::map<ui32,si32> *casualties) const; //casualties are array of maps size 2 (attacker, defeneder), maps are (crid => amount)
 
 	//void getPotentiallyAttackableHexes(AttackableTiles &at, const CStack* attacker, BattleHex destinationTile, BattleHex attackerPos); //hexes around target that could be attacked in melee

+ 9 - 3
lib/CBattleCallback.cpp

@@ -768,9 +768,9 @@ bool CBattleInfoCallback::battleCanShoot(const CStack * stack, BattleHex dest) c
 }
 
 TDmgRange CBattleInfoCallback::calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting,
-												 ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const
+												 ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const
 {
-	return calculateDmgRange(attacker, defender, attacker->count, shooting, charge, lucky, deathBlow, ballistaDoubleDmg);
+	return calculateDmgRange(attacker, defender, attacker->count, shooting, charge, lucky, unlucky, deathBlow, ballistaDoubleDmg);
 }
 
 TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) const
@@ -878,6 +878,11 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 	{
 		additiveBonus += 1.0;
 	}
+	//unlucky hit, used only if negative luck is enabled
+	if (info.unluckyHit)
+	{
+		additiveBonus -= 0.5; // FIXME: how bad (and luck in general) should work with following bonuses?
+	}
 
 	//ballista double dmg
 	if(info.ballistaDoubleDamage)
@@ -965,12 +970,13 @@ TDmgRange CBattleInfoCallback::calculateDmgRange(const BattleAttackInfo &info) c
 }
 
 TDmgRange CBattleInfoCallback::calculateDmgRange( const CStack* attacker, const CStack* defender, TQuantity attackerCount,
-	bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg ) const
+	bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg ) const
 {
 	BattleAttackInfo bai(attacker, defender, shooting);
 	bai.attackerCount = attackerCount;
 	bai.chargedFields = charge;
 	bai.luckyHit = lucky;
+	bai.unluckyHit = unlucky;
 	bai.deathBlow = deathBlow;
 	bai.ballistaDoubleDamage = ballistaDoubleDmg;
 	return calculateDmgRange(bai);

+ 3 - 2
lib/CBattleCallback.h

@@ -203,6 +203,7 @@ struct DLL_LINKAGE BattleAttackInfo
 	int chargedFields;
 
 	bool luckyHit;
+	bool unluckyHit;
 	bool deathBlow;
 	bool ballistaDoubleDamage;
 
@@ -235,8 +236,8 @@ public:
 	std::set<const CStack*>  batteAdjacentCreatures (const CStack * stack) const;
 	
 	TDmgRange calculateDmgRange(const BattleAttackInfo &info) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
-	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
-	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, ui8 charge, bool lucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
+	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, TQuantity attackerCount, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
+	TDmgRange calculateDmgRange(const CStack* attacker, const CStack* defender, bool shooting, ui8 charge, bool lucky, bool unlucky, bool deathBlow, bool ballistaDoubleDmg) const; //charge - number of hexes travelled before attack (for champion's jousting); returns pair <min dmg, max dmg>
 
 	//hextowallpart  //int battleGetWallUnderHex(BattleHex hex) const; //returns part of destructible wall / gate / keep under given hex or -1 if not found
 	std::pair<ui32, ui32> battleEstimateDamage(const BattleAttackInfo &bai, std::pair<ui32, ui32> * retaliationDmg = NULL) const; //estimates damage dealt by attacker to defender; it may be not precise especially when stack has randomly working bonuses; returns pair <min dmg, max dmg>

+ 4 - 1
lib/CCreatureHandler.cpp

@@ -272,7 +272,7 @@ std::vector<JsonNode> CCreatureHandler::loadLegacyData(size_t dataSize)
 	std::vector<JsonNode> h3Data;
 	h3Data.reserve(dataSize);
 
-	CLegacyConfigParser parser("DATA/ZCRTRAIT.TXT");
+	CLegacyConfigParser parser("DATA/CRTRAITS.TXT");
 
 	parser.endLine(); // header
 	parser.endLine();
@@ -619,7 +619,10 @@ void CCreatureHandler::loadCreatureJson(CCreature * creature, const JsonNode & c
 		BOOST_FOREACH(const JsonNode &ability, config["abilities"].Vector())
 		{
 			if (ability.getType() == JsonNode::DATA_VECTOR)
+			{
+				assert(0); // should be unused now
 				AddAbility(creature, ability.Vector()); // used only for H3 creatures
+			}
 			else
 			{
 				auto b = JsonUtils::parseBonus(ability);

+ 1 - 1
lib/CDefObjInfoHandler.cpp

@@ -50,7 +50,7 @@ CDefObjInfoHandler::CDefObjInfoHandler()
 {
 	VLC->dobjinfo = this;
 
-	auto textFile = CResourceHandler::get()->loadData(ResourceID("DATA/ZOBJCTS.TXT"));
+	auto textFile = CResourceHandler::get()->loadData(ResourceID("DATA/OBJECTS.TXT"));
 
 	std::istringstream inp(std::string((char*)textFile.first.get(), textFile.second));
 	int objNumber;

+ 2 - 2
lib/CGeneralTextHandler.cpp

@@ -163,7 +163,7 @@ CGeneralTextHandler::CGeneralTextHandler()
 	readToVector("DATA/RESTYPES.TXT", restypes);
 	readToVector("DATA/TERRNAME.TXT", terrainNames);
 	readToVector("DATA/RANDSIGN.TXT", randsign);
-	readToVector("DATA/ZCRGN1.TXT",   creGens);
+	readToVector("DATA/CRGEN1.TXT",   creGens);
 	readToVector("DATA/CRGEN4.TXT",   creGens4);
 	readToVector("DATA/OVERVIEW.TXT", overview);
 	readToVector("DATA/ARRAYTXT.TXT", arraytxt);
@@ -186,7 +186,7 @@ CGeneralTextHandler::CGeneralTextHandler()
 		while (parser.endLine());
 	}
 	{
-		CLegacyConfigParser parser("DATA/ZELP.TXT");
+		CLegacyConfigParser parser("DATA/HELP.TXT");
 		do
 		{
 			std::string first = parser.readString();

+ 1 - 0
lib/CMakeLists.txt

@@ -13,6 +13,7 @@ set(lib_SRCS
 		filesystem/CResourceLoader.cpp
 		filesystem/CFileInputStream.cpp
 		filesystem/CCompressedStream.cpp
+		filesystem/CMappedFileLoader.cpp
 		logging/CBasicLogConfigurator.cpp
 		logging/CLogger.cpp
 		mapping/CCampaignHandler.cpp

+ 7 - 30
lib/filesystem/CFilesystemLoader.h

@@ -33,36 +33,13 @@ public:
 	 */
 	explicit CFilesystemLoader(const std::string & baseDirectory, size_t depth = 16, bool initial = false);
 
-	/**
-	 * Loads a resource with the given resource name.
-	 *
-	 * @param resourceName The unqiue resource name in space of the filesystem.
-	 * @return a input stream object, not null
-	 */
-	std::unique_ptr<CInputStream> load(const std::string & resourceName) const;
-
-	/**
-	 * Checks if the file entry exists.
-	 *
-	 * @return true if the entry exists, false if not.
-	 */
-	bool existsEntry(const std::string & resourceName) const;
-
-	/**
-	 * Gets all entries in the filesystem.
-	 *
-	 * @return a list of all entries in the filesystem.
-	 */
-	boost::unordered_map<ResourceID, std::string> getEntries() const;
-
-	/**
-	 * Gets the origin of the archive loader.
-	 *
-	 * @return the file path to directory with archive (e.g. path/to/h3/mp3)
-	 */
-	std::string getOrigin() const;
-
-	bool createEntry(std::string filename);
+	/// Interface implementation
+	/// @see ISimpleResourceLoader
+	std::unique_ptr<CInputStream> load(const std::string & resourceName) const override;
+	bool existsEntry(const std::string & resourceName) const override;
+	boost::unordered_map<ResourceID, std::string> getEntries() const override;
+	std::string getOrigin() const override;
+	bool createEntry(std::string filename) override;
 
 private:
 	/** The base directory which is scanned and indexed. */

+ 7 - 37
lib/filesystem/CLodArchiveLoader.h

@@ -57,46 +57,16 @@ public:
 	 */
 	explicit CLodArchiveLoader(const std::string & archive);
 
-	/**
-	 * Loads a resource with the given resource name.
-	 *
-	 * @param resourceName The unqiue resource name in space of the archive.
-	 * @return a input stream object, not null.
-	 *
-	 * @throws std::runtime_error if the archive entry wasn't found
-	 */
-	std::unique_ptr<CInputStream> load(const std::string & resourceName) const;
+	/// Interface implementation
+	/// @see ISimpleResourceLoader
+	std::unique_ptr<CInputStream> load(const std::string & resourceName) const override;
+	boost::unordered_map<ResourceID, std::string> getEntries() const override;
+	bool existsEntry(const std::string & resourceName) const override;
+	std::string getOrigin() const override;
 
-	/**
-	 * Gets all entries in the archive.
-	 *
-	 * @return a list of all entries in the archive.
-	 */
-	boost::unordered_map<ResourceID, std::string> getEntries() const;
-
-	/**
-	 * Gets the archive entry for the requested resource
-	 *
-	 * @param resourceName The unqiue resource name in space of the archive.
-	 * @return the archive entry for the requested resource or a null ptr if the archive wasn't found
-	 */
+private:
 	const ArchiveEntry * getArchiveEntry(const std::string & resourceName) const;
 
-	/**
-	 * Checks if the archive entry exists.
-	 *
-	 * @return true if the entry exists, false if not.
-	 */
-	bool existsEntry(const std::string & resourceName) const;
-
-	/**
-	 * Gets the origin of the archive loader.
-	 *
-	 * @return the file path to the archive which is scanned and indexed.
-	 */
-	std::string getOrigin() const;
-
-private:
 	/**
 	 * Initializes a LOD archive.
 	 *

+ 45 - 0
lib/filesystem/CMappedFileLoader.cpp

@@ -0,0 +1,45 @@
+#include "StdInc.h"
+#include "CMappedFileLoader.h"
+#include "CResourceLoader.h"
+#include "../JsonNode.h"
+
+CMappedFileLoader::CMappedFileLoader(const JsonNode &config)
+{
+	BOOST_FOREACH(auto entry, config.Struct())
+	{
+		fileList[ResourceID(entry.first)] = entry.second.String();
+	}
+}
+
+std::unique_ptr<CInputStream> CMappedFileLoader::load(const std::string & resourceName) const
+{
+	return CResourceHandler::get()->load(ResourceID(resourceName));
+}
+
+bool CMappedFileLoader::existsEntry(const std::string & resourceName) const
+{
+	for(auto it = fileList.begin(); it != fileList.end(); ++it)
+	{
+		if(it->second == resourceName)
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+boost::unordered_map<ResourceID, std::string> CMappedFileLoader::getEntries() const
+{
+	return fileList;
+}
+
+std::string CMappedFileLoader::getOrigin() const
+{
+	return ""; // does not have any meaning with this type of data source
+}
+
+std::string CMappedFileLoader::getFullName(const std::string & resourceName) const
+{
+	return CResourceHandler::get()->getResourceName(ResourceID(resourceName));
+}

+ 53 - 0
lib/filesystem/CMappedFileLoader.h

@@ -0,0 +1,53 @@
+
+/*
+ * CMappedFileLoader.h, part of VCMI engine
+ *
+ * Authors: listed in file AUTHORS in main folder
+ *
+ * License: GNU General Public License v2.0 or later
+ * Full text of license available in license.txt file, in main folder
+ *
+ */
+
+#pragma once
+
+#include "ISimpleResourceLoader.h"
+#include "CResourceLoader.h"
+
+class CFileInfo;
+class CInputStream;
+
+/**
+ * Class that implements file mapping (aka *nix symbolic links)
+ * Uses json file as input, content is map:
+ * "fileA.txt" : "fileB.txt"
+ * Note that extension is necessary, but used only to determine type
+ *
+ * fileA - file which will be replaced
+ * fileB - file which will be used as replacement
+ */
+class DLL_LINKAGE CMappedFileLoader : public ISimpleResourceLoader
+{
+public:
+	/**
+	 * Ctor.
+	 *
+	 * @param config Specifies filesystem configuration
+	 */
+	explicit CMappedFileLoader(const JsonNode & config);
+
+	/// Interface implementation
+	/// @see ISimpleResourceLoader
+	std::unique_ptr<CInputStream> load(const std::string & resourceName) const override;
+	bool existsEntry(const std::string & resourceName) const override;
+	boost::unordered_map<ResourceID, std::string> getEntries() const override;
+	std::string getOrigin() const override;
+	std::string getFullName(const std::string & resourceName) const override;
+
+private:
+	/** A list of files in this map
+	 * key = ResourceID for resource loader
+	 * value = ResourceID to which file this request will be redirected
+	*/
+	boost::unordered_map<ResourceID, std::string> fileList;
+};

+ 31 - 1
lib/filesystem/CResourceLoader.cpp

@@ -3,6 +3,7 @@
 #include "CFileInfo.h"
 #include "CLodArchiveLoader.h"
 #include "CFilesystemLoader.h"
+#include "CMappedFileLoader.h"
 
 //For filesystem initialization
 #include "../JsonNode.h"
@@ -127,7 +128,7 @@ std::string CResourceLoader::getResourceName(const ResourceID & resourceIdent) c
 {
 	auto locator = getResource(resourceIdent);
 	if (locator.getLoader())
-		return locator.getLoader()->getOrigin() + '/' + locator.getResourceName();
+		return locator.getLoader()->getFullName(locator.getResourceName());
 	return "";
 }
 
@@ -372,6 +373,22 @@ void CResourceHandler::loadArchive(const std::string &prefix, const std::string
 		    shared_ptr<ISimpleResourceLoader>(new CLodArchiveLoader(filename)), false);
 }
 
+void CResourceHandler::loadJsonMap(const std::string &prefix, const std::string &mountPoint, const JsonNode & config)
+{
+	std::string URI = prefix + config["path"].String();
+	std::string filename = initialLoader->getResourceName(ResourceID(URI, EResType::TEXT));
+	if (!filename.empty())
+	{
+		auto configData = initialLoader->loadData(ResourceID(URI, EResType::TEXT));
+
+		const JsonNode config((char*)configData.first.get(), configData.second);
+
+		resourceLoader->addLoader(mountPoint,
+		    shared_ptr<ISimpleResourceLoader>(new CMappedFileLoader(config)), false);
+	}
+}
+
+
 void CResourceHandler::loadFileSystem(const std::string & prefix, const std::string &fsConfigURI)
 {
 	auto fsConfigData = initialLoader->loadData(ResourceID(fsConfigURI, EResType::TEXT));
@@ -390,6 +407,8 @@ void CResourceHandler::loadFileSystem(const std::string & prefix, const JsonNode
 			CStopWatch timer;
             logGlobal->debugStream() << "\t\tLoading resource at " << prefix + entry["path"].String();
 
+			if (entry["type"].String() == "map")
+				loadJsonMap(prefix, mountPoint.first, entry);
 			if (entry["type"].String() == "dir")
 				loadDirectory(prefix, mountPoint.first, entry);
 			if (entry["type"].String() == "lod")
@@ -423,8 +442,19 @@ std::vector<std::string> CResourceHandler::getAvailableMods()
 
 		name.erase(0, name.find_last_of('/') + 1);        //Remove path prefix
 
+		if (name == "WOG") // check if wog is actually present. Hack-ish but better than crash
+		{
+			if (!initialLoader->existsResource(ResourceID("ALL/DATA/ZVS", EResType::DIRECTORY)) &&
+			    !initialLoader->existsResource(ResourceID("ALL/MODS/WOG/DATA/ZVS", EResType::DIRECTORY)))
+			{
+				++iterator;
+				continue;
+			}
+		}
+
 		if (!name.empty()) // this is also triggered for "ALL/MODS/" entry
 			foundMods.push_back(name);
+
 		++iterator;
 	}
 	return foundMods;

+ 1 - 23
lib/filesystem/CResourceLoader.h

@@ -130,32 +130,9 @@ public:
 		return *this;
 	}
 
-	/**
-	 * Gets the name of the identifier.
-	 *
-	 * @return the name of the identifier
-	 */
 	std::string getName() const;
-
-	/**
-	 * Gets the type of the identifier.
-	 *
-	 * @return the type of the identifier
-	 */
 	EResType::Type getType() const;
-
-	/**
-	 * Sets the name of the identifier.
-	 *
-	 * @param name the name of the identifier. No extension, will be converted to uppercase.
-	 */
 	void setName(std::string name);
-
-	/**
-	 * Sets the type of the identifier.
-	 *
-	 * @param type the type of the identifier.
-	 */
 	void setType(EResType::Type type);
 
 protected:
@@ -390,6 +367,7 @@ public:
 	static void loadFileSystem(const std::string &prefix, const JsonNode & fsConfig);
 	static void loadDirectory(const std::string &prefix, const std::string & mountPoint, const JsonNode & config);
 	static void loadArchive(const std::string &prefix, const std::string & mountPoint, const JsonNode & config, EResType::Type archiveType);
+	static void loadJsonMap(const std::string &prefix, const std::string & mountPoint, const JsonNode & config);
 
 	/**
 	 * Checks all subfolders of MODS directory for presence of mods

+ 8 - 0
lib/filesystem/ISimpleResourceLoader.h

@@ -54,6 +54,14 @@ public:
 	 */
 	virtual std::string getOrigin() const =0;
 
+	/**
+	 * Gets full name of resource, e.g. name of file in filesystem.
+	 */
+	virtual std::string getFullName(const std::string & resourceName) const
+	{
+		return getOrigin() + '/' + resourceName;
+	}
+
 	/**
 	 * Creates new resource with specified filename.
 	 *

+ 15 - 9
server/CGameHandler.cpp

@@ -697,17 +697,23 @@ void CGameHandler::prepareAttack(BattleAttack &bat, const CStack *att, const CSt
 	int attackerLuck = att->LuckVal();
 	const CGHeroInstance * h0 = gs->curB->heroes[0],
 		* h1 = gs->curB->heroes[1];
-	bool noLuck = false;
-	if((h0 && NBonus::hasOfType(h0, Bonus::BLOCK_LUCK)) ||
-	   (h1 && NBonus::hasOfType(h1, Bonus::BLOCK_LUCK)))
-	{
-		noLuck = true;
-	}
 
-	if(!noLuck && attackerLuck > 0  &&  rand()%24 < attackerLuck) //TODO?: negative luck option?
+	if(!(h0 && NBonus::hasOfType(h0, Bonus::BLOCK_LUCK)) &&
+	   !(h1 && NBonus::hasOfType(h1, Bonus::BLOCK_LUCK)))
 	{
-		bat.flags |= BattleAttack::LUCKY;
+		if(attackerLuck > 0  && rand()%24 < attackerLuck)
+		{
+			bat.flags |= BattleAttack::LUCKY;
+		}
+		if (VLC->modh->settings.data["hardcodedFeatures"]["NEGATIVE_LUCK"].Bool()) // negative luck enabled
+		{
+			if (attackerLuck < 0 && rand()%24 < abs(attackerLuck))
+			{
+				bat.flags |= BattleAttack::UNLUCKY;
+			}
+		}
 	}
+
 	if (rand()%100 < att->valOfBonuses(Bonus::DOUBLE_DAMAGE_CHANCE))
 	{
 		bat.flags |= BattleAttack::DEATH_BLOW;
@@ -764,7 +770,7 @@ void CGameHandler::applyBattleEffects(BattleAttack &bat, const CStack *att, cons
 		bsa.flags |= BattleStackAttacked::SECONDARY; //all other targets do not suffer from spells & spell-like abilities
 	bsa.attackerID = att->ID;
 	bsa.stackAttacked = def->ID;
-	bsa.damageAmount = gs->curB->calculateDmg(att, def, gs->curB->battleGetOwner(att), gs->curB->battleGetOwner(def), bat.shot(), distance, bat.lucky(), bat.deathBlow(), bat.ballistaDoubleDmg());
+	bsa.damageAmount = gs->curB->calculateDmg(att, def, gs->curB->battleGetOwner(att), gs->curB->battleGetOwner(def), bat.shot(), distance, bat.lucky(), bat.unlucky(), bat.deathBlow(), bat.ballistaDoubleDmg());
 	def->prepareAttacked(bsa); //calculate casualties
 
 	//life drain handling