mirror of
https://github.com/vcmi/vcmi.git
synced 2025-11-25 22:42:04 +02:00
Merge branch 'develop' into feature/nullkiller2
This commit is contained in:
5
.github/workflows/github.yml
vendored
5
.github/workflows/github.yml
vendored
@@ -272,11 +272,10 @@ jobs:
|
|||||||
if: ${{ startsWith(matrix.platform, 'android') }}
|
if: ${{ startsWith(matrix.platform, 'android') }}
|
||||||
run: |
|
run: |
|
||||||
OUTPUT_DIRECTORY="${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/vcmi-app/build/outputs"
|
OUTPUT_DIRECTORY="${{ github.workspace }}/out/build/${{ matrix.preset }}/android-build/vcmi-app/build/outputs"
|
||||||
mv "$OUTPUT_DIRECTORY/apk/release/vcmi-release.apk" "${{ github.workspace }}/$VCMI_PACKAGE_FILE_NAME.apk"
|
mv "$OUTPUT_DIRECTORY/apk/release/vcmi-release.apk" "${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.apk"
|
||||||
mv "$OUTPUT_DIRECTORY/bundle/release/vcmi-release.aab" "${{ github.workspace }}/$VCMI_PACKAGE_FILE_NAME.aab"
|
mv "$OUTPUT_DIRECTORY/bundle/release/vcmi-release.aab" "${{github.workspace}}/out/build/${{matrix.preset}}/${{ env.VCMI_PACKAGE_FILE_NAME }}.aab"
|
||||||
|
|
||||||
- name: Upload Artifact
|
- name: Upload Artifact
|
||||||
if: ${{ matrix.pack == 1 }}
|
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
name: ${{ env.VCMI_PACKAGE_FILE_NAME }} - ${{ matrix.platform }}
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ void ObjectClusterizer::clusterizeObject(
|
|||||||
for(auto & path : pathCache)
|
for(auto & path : pathCache)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Checking path %s", path.toString());
|
logAi->trace("ObjectClusterizer Checking path %s", path.toString());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if(ai->heroManager->getHeroRole(path.targetHero) == HeroRole::SCOUT)
|
if(ai->heroManager->getHeroRole(path.targetHero) == HeroRole::SCOUT)
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ Goals::TGoalVec ClusterBehavior::decomposeCluster(const Nullkiller * ai, std::sh
|
|||||||
for(auto path = paths.begin(); path != paths.end();)
|
for(auto path = paths.begin(); path != paths.end();)
|
||||||
{
|
{
|
||||||
#if NKAI_TRACE_LEVEL >= 2
|
#if NKAI_TRACE_LEVEL >= 2
|
||||||
logAi->trace("Checking path %s", path->toString());
|
logAi->trace("ClusterBehavior Checking path %s", path->toString());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto blocker = ai->objectClusterizer->getBlocker(*path);
|
auto blocker = ai->objectClusterizer->getBlocker(*path);
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ void DeepDecomposer::decompose(TGoalVec & result, TSubgoal behavior, int depthLi
|
|||||||
// 0 - goals directly from behavior
|
// 0 - goals directly from behavior
|
||||||
Goals::TSubgoal task = depth >= 1 ? aggregateGoals(0, subgoal) : subgoal;
|
Goals::TSubgoal task = depth >= 1 ? aggregateGoals(0, subgoal) : subgoal;
|
||||||
|
|
||||||
|
// Issue with CGameHandler::spawnWanderingMonsters, see getFreeTiles(tiles, true);
|
||||||
|
// danger not linked GraphPaths::addChainInfo, so spawning only with nearby unblocked
|
||||||
#if NKAI_TRACE_LEVEL >= 1
|
#if NKAI_TRACE_LEVEL >= 1
|
||||||
logAi->trace("Found task %s", task->toString());
|
logAi->trace("Found task %s", task->toString());
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,19 +1,71 @@
|
|||||||
; VCMI Installer Script Instructions
|
; ============================================================================
|
||||||
|
; VCMI Installer – Adding a New Translation
|
||||||
; Steps to Add a New Translation to the Installer:
|
; ============================================================================
|
||||||
|
;
|
||||||
; 1. Download the ISL file for your language from the Inno Setup repository:
|
; 1. Download the base ISL file for your language:
|
||||||
; https://github.com/jrsoftware/issrc/tree/main/Files/Languages
|
; - Get the appropriate .isl file from the official Inno Setup repository:
|
||||||
;
|
; https://github.com/jrsoftware/issrc/tree/main/Files/Languages
|
||||||
; 2. Add the required VCMI custom messages to the downloaded ISL file.
|
;
|
||||||
; - Refer to the English.isl file for examples of the necessary custom messages.
|
; 2. Add VCMI custom messages:
|
||||||
; - Ensure all custom messages, including WindowsVersionNotSupported, are properly translated and aligned with the English version.
|
; - Open the downloaded .isl file and insert all VCMI-specific messages.
|
||||||
; 3. Update the ConfirmUninstall message:
|
; - Use English.isl (VCMI version) as a reference.
|
||||||
; - Custom Uninstall Wizard is used, ensure the ConfirmUninstall message is consistent with the English version and accurately reflects the intended functionality.
|
; - Ensure translations keep placeholders (%1, %2, etc.) intact and match
|
||||||
; 4. Update the WindowsVersionNotSupported message:
|
; the format of the English version exactly.
|
||||||
; - Ensure the WindowsVersionNotSupported message is consistent with the English version and accurately reflects the intended functionality.
|
;
|
||||||
; 5. Add the new language entry to the [Languages] section of the script:
|
; 3. Update/translate the following modified messages:
|
||||||
; - Use the correct syntax to include the language and its corresponding ISL file in the installer configuration.
|
; These differ from the default Inno Setup language files and are required
|
||||||
|
; for VCMI's custom installer functionality.
|
||||||
|
;
|
||||||
|
; ------------------------------------------------------------------------
|
||||||
|
; WindowsVersionNotSupported
|
||||||
|
; ------------------------------------------------------------------------
|
||||||
|
; Why: VCMI adds a more descriptive message for unsupported Windows versions.
|
||||||
|
; Original:
|
||||||
|
; WindowsVersionNotSupported=This program does not support the version of Windows your computer is running.
|
||||||
|
; VCMI version:
|
||||||
|
; WindowsVersionNotSupported=This program cannot run on your version of Windows. Please ensure you are using the correct Windows version.
|
||||||
|
;
|
||||||
|
; ------------------------------------------------------------------------
|
||||||
|
; PrivilegesRequiredOverride* messages
|
||||||
|
; ------------------------------------------------------------------------
|
||||||
|
; Why: VCMI customizes privilege escalation messages to clarify installation
|
||||||
|
; for all users vs. current user and highlight administrative rights.
|
||||||
|
; Messages to add/update:
|
||||||
|
; PrivilegesRequiredOverrideTitle
|
||||||
|
; PrivilegesRequiredOverrideInstruction
|
||||||
|
; PrivilegesRequiredOverrideText1
|
||||||
|
; PrivilegesRequiredOverrideText2
|
||||||
|
; PrivilegesRequiredOverrideAllUsers
|
||||||
|
; PrivilegesRequiredOverrideAllUsersRecommended
|
||||||
|
; PrivilegesRequiredOverrideCurrentUser
|
||||||
|
; PrivilegesRequiredOverrideCurrentUserRecommended
|
||||||
|
;
|
||||||
|
; Example (VCMI English):
|
||||||
|
; PrivilegesRequiredOverrideTitle=Administrator Privileges Required
|
||||||
|
; PrivilegesRequiredOverrideInstruction=Choose how to run the installer
|
||||||
|
; PrivilegesRequiredOverrideText1=%1 requires administrative rights to install for all users. You can also install just for your account (no administrative rights required) or for all users (administrator rights required).
|
||||||
|
; PrivilegesRequiredOverrideText2=%1 can be installed only for your account (no administrative rights required) or for all users (administrator rights required).
|
||||||
|
; PrivilegesRequiredOverrideAllUsers=Run as &Administrator (install for all users)
|
||||||
|
; PrivilegesRequiredOverrideAllUsersRecommended=Run as &Administrator (recommended)
|
||||||
|
; PrivilegesRequiredOverrideCurrentUser=Run as &Standard User (install for me only)
|
||||||
|
; PrivilegesRequiredOverrideCurrentUserRecommended=Run as &Standard User (recommended)
|
||||||
|
;
|
||||||
|
; ------------------------------------------------------------------------
|
||||||
|
; ConfirmUninstall
|
||||||
|
; ------------------------------------------------------------------------
|
||||||
|
; Why: VCMI uses a custom uninstall wizard. The message must reflect this.
|
||||||
|
; Original:
|
||||||
|
; ConfirmUninstall=Are you sure you want to completely remove %1 and all of its components?
|
||||||
|
; VCMI version:
|
||||||
|
; ConfirmUninstall=Are you sure you want to run the %1 uninstall wizard?
|
||||||
|
;
|
||||||
|
; 4. Add the new language to the installer:
|
||||||
|
; - In the [Languages] section of the script, register your translation:
|
||||||
|
; Name: "YourLanguage"; MessagesFile: "{#LangPath}\YourLanguage.isl"
|
||||||
|
;
|
||||||
|
; 5. Verify consistency:
|
||||||
|
; - Check all custom messages against the English VCMI ISL file.
|
||||||
|
; - Test the installer to confirm all messages appear correctly.
|
||||||
|
|
||||||
|
|
||||||
; Manual preprocessor definitions are provided using ISCC.exe parameters.
|
; Manual preprocessor definitions are provided using ISCC.exe parameters.
|
||||||
@@ -123,14 +175,13 @@ Name: "{code:GetUserDesktopFolder}\{cm:ShortcutLauncher}"; Filename: "{app}\VCMI
|
|||||||
|
|
||||||
|
|
||||||
[Tasks]
|
[Tasks]
|
||||||
Name: "desktop"; Description: "{cm:CreateDesktopShortcuts}"; GroupDescription: "{cm:SystemIntegration}"
|
Name: "desktop"; Description: "{cm:CreateDesktopShortcuts}"; GroupDescription: "{cm:SystemIntegration}"; Check: not IsPRInstaller
|
||||||
Name: "startmenu"; Description: "{cm:CreateStartMenuShortcuts}"; GroupDescription: "{cm:SystemIntegration}"
|
Name: "startmenu"; Description: "{cm:CreateStartMenuShortcuts}"; GroupDescription: "{cm:SystemIntegration}"; Check: not IsPRInstaller
|
||||||
Name: "fileassociation_h3m"; Description: "{cm:AssociateH3MFiles}"; GroupDescription: "{cm:SystemIntegration}"; Flags: unchecked
|
Name: "fileassociation_h3m"; Description: "{cm:AssociateH3MFiles}"; GroupDescription: "{cm:SystemIntegration}"; Flags: unchecked; Check: not IsPRInstaller
|
||||||
Name: "fileassociation_vcmimap"; Description: "{cm:AssociateVCMIMapFiles}"; GroupDescription: "{cm:SystemIntegration}"
|
Name: "fileassociation_vcmimap"; Description: "{cm:AssociateVCMIMapFiles}"; GroupDescription: "{cm:SystemIntegration}"; Check: not IsPRInstaller
|
||||||
|
|
||||||
Name: "firewallrules"; Description: "{cm:AddFirewallRules}"; GroupDescription: "{cm:VCMISettings}"; Check: IsAdminInstallMode
|
|
||||||
Name: "h3copyfiles"; Description: "{cm:CopyH3Files}"; GroupDescription: "{cm:VCMISettings}"; Check: IsHeroes3Installed and IsCopyFilesNeeded
|
|
||||||
|
|
||||||
|
Name: "firewallrules"; Description: "{cm:AddFirewallRules}"; GroupDescription: "{cm:VCMISettings}"; Check: not IsPRInstaller and IsAdminInstallMode
|
||||||
|
Name: "h3copyfiles"; Description: "{cm:CopyH3Files}"; GroupDescription: "{cm:VCMISettings}"; Check: not IsPRInstaller and IsHeroes3Installed and IsCopyFilesNeeded
|
||||||
|
|
||||||
[Registry]
|
[Registry]
|
||||||
Root: HKCU; Subkey: "Software\{#VCMIFolder}"; ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"; Flags: uninsdeletekey
|
Root: HKCU; Subkey: "Software\{#VCMIFolder}"; ValueType: string; ValueName: "InstallPath"; ValueData: "{app}"; Flags: uninsdeletekey
|
||||||
@@ -328,7 +379,7 @@ begin
|
|||||||
// For 32-bit installer on ARM64, return the 32-bit Program Files directory
|
// For 32-bit installer on ARM64, return the 32-bit Program Files directory
|
||||||
Result := ExpandConstant('{commonpf32}')
|
Result := ExpandConstant('{commonpf32}')
|
||||||
else
|
else
|
||||||
// For AMR64 installer, return the Program Files directory
|
// For AMR64 installer, return the Program Files directory
|
||||||
Result := ExpandConstant('{commonpf}')
|
Result := ExpandConstant('{commonpf}')
|
||||||
end
|
end
|
||||||
else if IsWin64 then
|
else if IsWin64 then
|
||||||
@@ -382,21 +433,18 @@ end;
|
|||||||
|
|
||||||
procedure OnTaskCheck(Sender: TObject);
|
procedure OnTaskCheck(Sender: TObject);
|
||||||
var
|
var
|
||||||
i: Integer;
|
idx: Integer;
|
||||||
begin
|
begin
|
||||||
// Loop through all tasks in the tasks list
|
// Get the index of the currently clicked task
|
||||||
for i := 0 to WizardForm.TasksList.Items.Count - 1 do
|
idx := WizardForm.TasksList.ItemIndex;
|
||||||
|
|
||||||
|
// Check if the clicked task is the "AddFirewallRules" one
|
||||||
|
if WizardForm.TasksList.Items[idx] = ExpandConstant('{cm:AddFirewallRules}') then
|
||||||
begin
|
begin
|
||||||
// Check if the current task is "firewallrules"
|
// If it was just unchecked, show the warning
|
||||||
if WizardForm.TasksList.Items[i] = ExpandConstant('{cm:AddFirewallRules}') then
|
if not WizardForm.TasksList.Checked[idx] then
|
||||||
begin
|
begin
|
||||||
// Check if the "firewallrules" task is unchecked
|
MsgBox(ExpandConstant('{cm:Warning}') + '!' + #13#10 + #13#10 + ExpandConstant('{cm:InstallForMeOnly1}') + #13#10 + ExpandConstant('{cm:InstallForMeOnly2}'), mbError, MB_OK);
|
||||||
if not WizardForm.TasksList.Checked[i] then
|
|
||||||
begin
|
|
||||||
// Show a custom warning message box
|
|
||||||
MsgBox(ExpandConstant('{cm:Warning}') + '!' + #13#10 + #13#10 + ExpandConstant('{cm:InstallForMeOnly1}') + #13#10 + ExpandConstant('{cm:InstallForMeOnly2}'), mbError, MB_OK);
|
|
||||||
end;
|
|
||||||
Exit; // Task found, exit the loop
|
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -425,24 +473,27 @@ function IsUCRTNeeded: Boolean;
|
|||||||
var
|
var
|
||||||
FileName: String;
|
FileName: String;
|
||||||
begin
|
begin
|
||||||
Result := False; // Default to not copying files
|
Result := True; // Default to copy the file
|
||||||
|
|
||||||
// Normalize and extract the file name from CurrentFileName
|
|
||||||
FileName := ExtractFileName(ExpandConstant(CurrentFileName));
|
FileName := ExtractFileName(ExpandConstant(CurrentFileName));
|
||||||
|
|
||||||
// Check for file existence based on architecture
|
// Only check system if the file name contains "api"
|
||||||
if IsWin64 then
|
if Pos('API', UpperCase(FileName)) = 1 then
|
||||||
begin
|
begin
|
||||||
if ExpandConstant('{#InstallerArch}') = 'x64' then
|
// Check existence based on architecture
|
||||||
// For 64-bit installer on 64-bit OS, check System32
|
if IsWin64 then
|
||||||
Result := not FileExists(ExpandConstant('{win}\System32\' + FileName))
|
begin
|
||||||
|
if ExpandConstant('{#InstallerArch}') = 'x64' then
|
||||||
|
// For 64-bit installer on 64-bit OS, check System32
|
||||||
|
Result := not FileExists(ExpandConstant('{win}\System32\' + FileName))
|
||||||
|
else
|
||||||
|
// For 32-bit installer on 64-bit OS, check SysWOW64
|
||||||
|
Result := not FileExists(ExpandConstant('{win}\SysWOW64\' + FileName));
|
||||||
|
end
|
||||||
else
|
else
|
||||||
// For 32-bit installer on 64-bit OS, check SysWOW64
|
// For 32-bit OS, always check System32
|
||||||
Result := not FileExists(ExpandConstant('{win}\SysWOW64\' + FileName));
|
Result := not FileExists(ExpandConstant('{win}\System32\' + FileName));
|
||||||
end
|
end;
|
||||||
else
|
|
||||||
// For 32-bit OS, always check System32
|
|
||||||
Result := not FileExists(ExpandConstant('{win}\System32\' + FileName));
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
@@ -464,6 +515,14 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
function IsPRInstaller(): Boolean;
|
||||||
|
begin
|
||||||
|
// Skip Tasks page if this is a PR build
|
||||||
|
Result := Pos('-PR-', ExpandConstant('{#InstallerName}')) > 0;
|
||||||
|
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
function InitializeSetup(): Boolean;
|
function InitializeSetup(): Boolean;
|
||||||
var
|
var
|
||||||
InstallPath: String;
|
InstallPath: String;
|
||||||
@@ -578,7 +637,21 @@ end;
|
|||||||
function ShouldSkipPage(PageID: Integer): Boolean;
|
function ShouldSkipPage(PageID: Integer): Boolean;
|
||||||
begin
|
begin
|
||||||
Result := False; // Default is not to skip the page
|
Result := False; // Default is not to skip the page
|
||||||
|
|
||||||
|
// Don't skip Target page if this is a PR build and upgrade
|
||||||
|
if IsPRInstaller and IsUpgrade and (PageID = wpSelectDir) then
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Skip Tasks page if this is a PR build
|
||||||
|
if IsPRInstaller and (PageID = wpSelectTasks) then
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
|
||||||
if IsUpgrade then
|
if IsUpgrade then
|
||||||
begin
|
begin
|
||||||
if (PageID = wpLicense) or (PageID = wpSelectTasks) or (PageID = wpReady) then
|
if (PageID = wpLicense) or (PageID = wpSelectTasks) or (PageID = wpReady) then
|
||||||
@@ -594,7 +667,6 @@ procedure CurPageChanged(CurPageID: Integer);
|
|||||||
begin
|
begin
|
||||||
// Ensure the footer message is visible on every page
|
// Ensure the footer message is visible on every page
|
||||||
FooterLabel.Visible := True;
|
FooterLabel.Visible := True;
|
||||||
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
@@ -678,12 +750,42 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
|
procedure CreateDefaultSettingsFile();
|
||||||
|
var
|
||||||
|
ConfigDir, SettingsFile, Language, JSONContent: String;
|
||||||
|
begin
|
||||||
|
ConfigDir := GlobalUserDocsFolder + '\' + '{#VCMIFilesFolder}' + '\config';
|
||||||
|
SettingsFile := ConfigDir + '\settings.json';
|
||||||
|
|
||||||
|
if not FileExists(SettingsFile) then
|
||||||
|
begin
|
||||||
|
Language := ActiveLanguage;
|
||||||
|
if Language = '' then
|
||||||
|
Language := 'english';
|
||||||
|
|
||||||
|
JSONContent :=
|
||||||
|
'{' + #13#10 +
|
||||||
|
Chr(9) + '"general" : {' + #13#10 +
|
||||||
|
Chr(9) + Chr(9) + '"language" : "' + Language + '"' + #13#10 +
|
||||||
|
Chr(9) + '}' + #13#10 +
|
||||||
|
'}';
|
||||||
|
|
||||||
|
if not DirExists(ConfigDir) then
|
||||||
|
ForceDirectories(ConfigDir);
|
||||||
|
|
||||||
|
SaveStringToFile(SettingsFile, JSONContent, False);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
procedure RunPreInstallTasks();
|
procedure RunPreInstallTasks();
|
||||||
begin
|
begin
|
||||||
// Remove Legacy installer when needed
|
// Remove Legacy installer when needed
|
||||||
RemoveLegacyInstaller();
|
RemoveLegacyInstaller();
|
||||||
// Copy H3 files when needed
|
// Copy H3 files when needed
|
||||||
PerformHeroes3FileCopy();
|
PerformHeroes3FileCopy();
|
||||||
|
// Create default language JSON - for future use
|
||||||
|
// CreateDefaultSettingsFile();
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|
||||||
@@ -863,3 +965,4 @@ begin
|
|||||||
Abort;
|
Abort;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -468,11 +468,15 @@ endif()
|
|||||||
# Finding packages #
|
# Finding packages #
|
||||||
############################################
|
############################################
|
||||||
|
|
||||||
set(BOOST_COMPONENTS date_time filesystem locale program_options system)
|
set(BOOST_COMPONENTS date_time filesystem locale program_options)
|
||||||
if(ENABLE_INNOEXTRACT)
|
if(ENABLE_INNOEXTRACT)
|
||||||
list(APPEND BOOST_COMPONENTS iostreams)
|
list(APPEND BOOST_COMPONENTS iostreams)
|
||||||
endif()
|
endif()
|
||||||
find_package(Boost 1.48.0 REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
find_package(Boost 1.48.0 REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||||
|
if(Boost_MAJOR_VERSION EQUAL 1 AND Boost_MINOR_VERSION LESS 69)
|
||||||
|
list(APPEND BOOST_COMPONENTS system)
|
||||||
|
find_package(Boost 1.48.0 REQUIRED COMPONENTS ${BOOST_COMPONENTS})
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB REQUIRED)
|
||||||
# Conan compatibility
|
# Conan compatibility
|
||||||
|
|||||||
@@ -8,84 +8,87 @@ To use cheat code, press `Tab` key or click/tap on status bar to open game chat
|
|||||||
|
|
||||||
### Spells
|
### Spells
|
||||||
|
|
||||||
`nwcthereisnospoon`, `nwcmidichlorians`, `nwctim`, `vcmiistari` or `vcmispells` - give a spell book, all spells and 999 mana to currently selected hero. Also allows casting spell up to 100 times per combat round
|
- `nwcthereisnospoon`, `nwcmidichlorians`, `nwctim`, `vcmiistari` or `vcmispells` - give a spell book, all spells and 999 mana to currently selected hero. Also allows casting spell up to 100 times per combat round
|
||||||
|
|
||||||
### Secondary Skills
|
### Secondary Skills
|
||||||
|
|
||||||
`vcmiskill <skillID> <mastery>` - give a secondary skill to currently selected hero
|
- `vcmiskill <skillID> <mastery>` - give a secondary skill to currently selected hero
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
`vcmiskill learning` - give expert level learning skill
|
|
||||||
`vcmiskill leadership 2` - give advanced level leadership skill
|
- `vcmiskill learning` - give expert level learning skill
|
||||||
`vcmiskill wisdom 0` - remove wisdom skill
|
- `vcmiskill leadership 2` - give advanced level leadership skill
|
||||||
`vcmiskill every` - give all skills on expert level
|
- `vcmiskill wisdom 0` - remove wisdom skill
|
||||||
`vcmiskill every 0` - remove all skills
|
- `vcmiskill every` - give all skills on expert level
|
||||||
|
- `vcmiskill every 0` - remove all skills
|
||||||
|
|
||||||
### Army
|
### Army
|
||||||
|
|
||||||
`nwctrinity`, `nwcpadme`, `nwcavertingoureyes`, `vcmiainur` or `vcmiarchangel` - give 5 Archangels in every empty slot (to currently selected hero)
|
- `nwctrinity`, `nwcpadme`, `nwcavertingoureyes`, `vcmiainur` or `vcmiarchangel` - give 5 Archangels in every empty slot (to currently selected hero)
|
||||||
`nwcagents`, `nwcdarthmaul`, `nwcfleshwound` or `vcmiangband` or `vcmiblackknight` - give 10 black knight in every empty slot
|
- `nwcagents`, `nwcdarthmaul`, `nwcfleshwound` or `vcmiangband` or `vcmiblackknight` - give 10 black knight in every empty slot
|
||||||
`vcmiglaurung` or `vcmicrystal` - give 5000 crystal dragons in every empty slot
|
- `vcmiglaurung` or `vcmicrystal` - give 5000 crystal dragons in every empty slot
|
||||||
`vcmiazure` - give 5000 azure dragons in every empty slot
|
- `vcmiazure` - give 5000 azure dragons in every empty slot
|
||||||
`vcmifaerie` - give 5000 faerie dragons in every empty slot
|
- `vcmifaerie` - give 5000 faerie dragons in every empty slot
|
||||||
|
|
||||||
|
- Alternative usage: `vcmiarmy <creatureID> <amount>`
|
||||||
|
|
||||||
Alternative usage: `vcmiarmy <creatureID> <amount>`
|
|
||||||
Gives specific creature in every slot, with optional amount. Examples:
|
Gives specific creature in every slot, with optional amount. Examples:
|
||||||
`vcmiarmy imp` - give 5, 50, 500... 500k imps in every free slot
|
|
||||||
`vcmiarmy grandElf 100` - gives 100 grand elves in every free slot
|
- `vcmiarmy imp` - give 5, 50, 500... 500k imps in every free slot
|
||||||
|
- `vcmiarmy grandElf 100` - gives 100 grand elves in every free slot
|
||||||
|
|
||||||
### Town buildings
|
### Town buildings
|
||||||
|
|
||||||
`nwczion`, `nwccoruscant`, `nwconlyamodel`, `vcmiarmenelos` or `vcmibuild` - build all buildings in currently selected town
|
- `nwczion`, `nwccoruscant`, `nwconlyamodel`, `vcmiarmenelos` or `vcmibuild` - build all buildings in currently selected town
|
||||||
|
|
||||||
### Artifacts
|
### Artifacts
|
||||||
|
|
||||||
`nwclotsofguns`, `nwcr2d2`, `nwcantioch`, `vcminoldor` or `vcmimachines` - give ballista, ammo cart and first aid tent
|
- `nwclotsofguns`, `nwcr2d2`, `nwcantioch`, `vcminoldor` or `vcmimachines` - give ballista, ammo cart and first aid tent
|
||||||
`vcmiforgeofnoldorking` or `vcmiartifacts` - give all artifacts, except spell book, spell scrolls and war machines. Artifacts added via mods included
|
- `vcmiforgeofnoldorking` or `vcmiartifacts` - give all artifacts, except spell book, spell scrolls and war machines. Artifacts added via mods included
|
||||||
`vcmiscrolls` - give spell scrolls for every possible spells
|
- `vcmiscrolls` - give spell scrolls for every possible spells
|
||||||
|
|
||||||
### Movement points
|
### Movement points
|
||||||
|
|
||||||
`nwcnebuchadnezzar`, `nwcpodracer`, `nwccoconuts`, `vcminahar` or `vcmimove` - give unlimited (or specified amount of) movement points and free ship boarding
|
- `nwcnebuchadnezzar`, `nwcpodracer`, `nwccoconuts`, `vcminahar` or `vcmimove` - give unlimited (or specified amount of) movement points and free ship boarding
|
||||||
Alternative usage: `vcmimove <amount>` - gives specified amount of movement points
|
- Alternative usage: `vcmimove <amount>` - gives specified amount of movement points
|
||||||
|
|
||||||
### Resources
|
### Resources
|
||||||
|
|
||||||
`nwctheconstruct`, `nwcwatto`, `nwcshrubbery`, `vcmiformenos` or `vcmiresources` - give resources (100000 gold, 100 of wood, ore and rare resources)
|
- `nwctheconstruct`, `nwcwatto`, `nwcshrubbery`, `vcmiformenos` or `vcmiresources` - give resources (100000 gold, 100 of wood, ore and rare resources)
|
||||||
Alternative usage: `vcmiresources <amount>` - gives specified amount of all resources and x1000 of gold
|
- Alternative usage: `vcmiresources <amount>` - gives specified amount of all resources and x1000 of gold
|
||||||
|
|
||||||
### Fog of War
|
### Fog of War
|
||||||
|
|
||||||
`nwcwhatisthematrix`, `nwcrevealourselves`, `nwcgeneraldirection`, `vcmieagles` or `vcmimap` - reveal Fog of War
|
- `nwcwhatisthematrix`, `nwcrevealourselves`, `nwcgeneraldirection`, `vcmieagles` or `vcmimap` - reveal Fog of War
|
||||||
`nwcignoranceisbliss`, `vcmiungoliant` or `vcmihidemap` - conceal Fog of War
|
- `nwcignoranceisbliss`, `vcmiungoliant` or `vcmihidemap` - conceal Fog of War
|
||||||
|
|
||||||
### Experience
|
### Experience
|
||||||
|
|
||||||
`nwcneo`, `nwcquigon`, `nwcigotbetter`, `vcmiglorfindel` or `vcmilevel` - advances currently selected hero to the next level
|
- `nwcneo`, `nwcquigon`, `nwcigotbetter`, `vcmiglorfindel` or `vcmilevel` - advances currently selected hero to the next level
|
||||||
Alternative usage: `vcmilevel <amount>` - advances hero by specified number of levels
|
- Alternative usage: `vcmilevel <amount>` - advances hero by specified number of levels
|
||||||
|
|
||||||
- `vcmiolorin` or `vcmiexp` - gives selected hero 10000 experience
|
- `vcmiolorin` or `vcmiexp` - gives selected hero 10000 experience
|
||||||
Alternative usage: `vcmiexp <amount>` - gives selected hero specified amount of experience
|
- Alternative usage: `vcmiexp <amount>` - gives selected hero specified amount of experience
|
||||||
|
|
||||||
### Luck and morale
|
### Luck and morale
|
||||||
|
|
||||||
`nwcfollowthewhiterabbit`, `nwccastleanthrax` or `vcmiluck` - the currently selected hero permanently gains maximum luck
|
- `nwcfollowthewhiterabbit`, `nwccastleanthrax` or `vcmiluck` - the currently selected hero permanently gains maximum luck
|
||||||
`nwcmorpheus`, `nwcmuchrejoicing` or `vcmimorale` - the currently selected hero permanently gains maximum morale
|
- `nwcmorpheus`, `nwcmuchrejoicing` or `vcmimorale` - the currently selected hero permanently gains maximum morale
|
||||||
|
|
||||||
### Puzzle map
|
### Puzzle map
|
||||||
|
|
||||||
`nwcoracle`, `nwcprophecy`, `nwcalreadygotone` or `vcmiobelisk` - reveals the puzzle map
|
- `nwcoracle`, `nwcprophecy`, `nwcalreadygotone` or `vcmiobelisk` - reveals the puzzle map
|
||||||
|
|
||||||
### Finishing the game
|
### Finishing the game
|
||||||
|
|
||||||
`nwcredpill`, `nwctrojanrabbit`, `vcmisilmaril` or `vcmiwin` - player wins
|
- `nwcredpill`, `nwctrojanrabbit`, `vcmisilmaril` or `vcmiwin` - player wins
|
||||||
`nwcbluepill`, `nwcsirrobin`, `vcmimelkor` or `vcmilose` - player loses
|
- `nwcbluepill`, `nwcsirrobin`, `vcmimelkor` or `vcmilose` - player loses
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
|
|
||||||
`nwctheone` or `vcmigod` - reveals the whole map, gives 5 archangels in each empty slot, unlimited movement points and permanent flight
|
- `nwctheone` or `vcmigod` - reveals the whole map, gives 5 archangels in each empty slot, unlimited movement points and permanent flight
|
||||||
`nwcphisherprice` or `vcmicolor` - change game color palette to Heroes II like until game restart
|
- `nwcphisherprice` or `vcmicolor` - change game color palette to Heroes II like until game restart
|
||||||
`vcmigray` - change game color palette to grayscale until game restart
|
- `vcmigray` - change game color palette to grayscale until game restart
|
||||||
|
|
||||||
## Using cheat codes on other players
|
## Using cheat codes on other players
|
||||||
|
|
||||||
@@ -97,10 +100,10 @@ By default, all cheat codes apply to current player. Alternatively, it is possib
|
|||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
`vcmieagles blue` - reveal FoW only for blue player
|
- `vcmieagles blue` - reveal FoW only for blue player
|
||||||
`vcmieagles ai` - reveal FoW only for AI players
|
- `vcmieagles ai` - reveal FoW only for AI players
|
||||||
`vcmieagles all` - reveal FoW for all players on map
|
- `vcmieagles all` - reveal FoW for all players on map
|
||||||
`vcminahar ai` - give 1000000 movement points to each hero of every AI player
|
- `vcminahar ai` - give 1000000 movement points to each hero of every AI player
|
||||||
|
|
||||||
## Multiplayer chat commands
|
## Multiplayer chat commands
|
||||||
|
|
||||||
@@ -116,10 +119,10 @@ Following commands can be used by any player in multiplayer:
|
|||||||
- `!help` - displays in-game list of available commands
|
- `!help` - displays in-game list of available commands
|
||||||
- `!cheaters` - lists players that have entered cheat at any point of the game
|
- `!cheaters` - lists players that have entered cheat at any point of the game
|
||||||
- `!vote` - initiates voting to change one of the possible options:
|
- `!vote` - initiates voting to change one of the possible options:
|
||||||
- - `!vote simturns allow X` - allow simultaneous turns for specified number of days, or until contact
|
- `!vote simturns allow X` - allow simultaneous turns for specified number of days, or until contact
|
||||||
- - `!vote simturns force X` - force simultaneous turns for specified number of days, blocking player contacts
|
- `!vote simturns force X` - force simultaneous turns for specified number of days, blocking player contacts
|
||||||
- - `!vote simturns abort` - abort simultaneous turns once this turn ends
|
- `!vote simturns abort` - abort simultaneous turns once this turn ends
|
||||||
- - `!vote timer prolong X` - prolong base timer for all players by specified number of seconds
|
- `!vote timer prolong X` - prolong base timer for all players by specified number of seconds
|
||||||
|
|
||||||
## Client Commands
|
## Client Commands
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,16 @@
|
|||||||
- manually: <https://github.com/vcmi/vcmi/releases/latest>
|
- manually: <https://github.com/vcmi/vcmi/releases/latest>
|
||||||
- via Homebrew: `brew install --cask --no-quarantine vcmi/vcmi/vcmi`
|
- via Homebrew: `brew install --cask --no-quarantine vcmi/vcmi/vcmi`
|
||||||
- Daily builds (might be unstable)
|
- Daily builds (might be unstable)
|
||||||
- Intel (x86_64) builds: <https://builds.vcmi.download/branch/develop/macOS/intel>
|
- [Intel (x86_64) builds](https://builds.vcmi.download/branch/develop/macos-arm/)
|
||||||
- Apple Silicon (arm64) builds: <https://builds.vcmi.download/branch/develop/macOS/arm>
|
- [Apple Silicon (arm64) builds](https://builds.vcmi.download/branch/develop/macos-intel/)
|
||||||
|
|
||||||
If the app doesn't open, right-click the app bundle - select *Open* menu item - press *Open* button. You may also need to allow running it in System Settings - Privacy & Security.
|
If the app doesn't open, right-click the app bundle - select *Open* menu item - press *Open* button. On macOS 15 Sequoia and later there will be no *Open* button, instead you'll see the following dialog:
|
||||||
|
|
||||||
Please report about gameplay problem on forums: [Help & Bugs](https://forum.vcmi.eu/c/international-board/help-bugs) Make sure to specify what hardware and macOS version you use.
|

|
||||||
|
|
||||||
|
To fix it, go to System Settings - Privacy & Security tab - scroll down and press the *Open Anyway* button (marked with an arrow on the image below). After that confirm your action and enter your administrator password.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Step 2: Installing Heroes III data files
|
## Step 2: Installing Heroes III data files
|
||||||
|
|
||||||
|
|||||||
BIN
docs/players/images/macos15-open.png
Normal file
BIN
docs/players/images/macos15-open.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
BIN
docs/players/images/macos15-privacy.png
Normal file
BIN
docs/players/images/macos15-privacy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
Submodule launcher/lib/innoextract updated: 9977089412...98bb55798a
@@ -791,7 +791,7 @@ bool CGameInfoCallback::isTeleportEntrancePassable(const CGTeleport * obj, Playe
|
|||||||
return obj && obj->isEntrance() && !isTeleportChannelImpassable(obj->channel, player);
|
return obj && obj->isEntrance() && !isTeleportChannelImpassable(obj->channel, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
|
void CGameInfoCallback::getFreeTiles(std::vector<int3> & tiles, bool skipIfNearbyGuarded) const
|
||||||
{
|
{
|
||||||
std::vector<int> floors;
|
std::vector<int> floors;
|
||||||
floors.reserve(gameState().getMap().levels());
|
floors.reserve(gameState().getMap().levels());
|
||||||
@@ -808,7 +808,21 @@ void CGameInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
|
|||||||
{
|
{
|
||||||
tinfo = getTile(int3 (xd,yd,zd));
|
tinfo = getTile(int3 (xd,yd,zd));
|
||||||
if (tinfo->isLand() && tinfo->getTerrain()->isPassable() && !tinfo->blocked()) //land and free
|
if (tinfo->isLand() && tinfo->getTerrain()->isPassable() && !tinfo->blocked()) //land and free
|
||||||
|
{
|
||||||
|
// Ensure that CGameHandler::spawnWanderingMonsters won't set a random monster next to another monster
|
||||||
|
// because Nullkiller AI is not able to go to one monster without falling into the attack range of the nearby one
|
||||||
|
// See GraphPaths::addChainInfo if(node.linkDanger > 0) (no link between random monsters and map monsters)
|
||||||
|
// Ivan: When new monster spawns, AI should receive AIGateway::newObject call for each object visible to AI. Probably you need to invalidate
|
||||||
|
// that data for AI & force recalculation on next turn. New queries to gamestate, like getGuardingCreaturePosition that are done either
|
||||||
|
// from AIGateway::newObject method or at any point later should correctly include newly spawned monsters
|
||||||
|
// TODO: Ensure this linking issue is properly fixed, not just with the workaround below
|
||||||
|
if (skipIfNearbyGuarded && guardingCreaturePosition(int3 (xd,yd,zd)).isValid())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
tiles.emplace_back(xd, yd, zd);
|
tiles.emplace_back(xd, yd, zd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ public:
|
|||||||
bool isTeleportEntrancePassable(const CGTeleport * obj, PlayerColor player) const override;
|
bool isTeleportEntrancePassable(const CGTeleport * obj, PlayerColor player) const override;
|
||||||
|
|
||||||
//used for random spawns
|
//used for random spawns
|
||||||
void getFreeTiles(std::vector<int3> &tiles) const;
|
void getFreeTiles(std::vector<int3> &tiles, bool skipIfNearbyGuarded) const;
|
||||||
void getTilesInRange(FowTilesType & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player = std::optional<PlayerColor>(), int3::EDistanceFormula formula = int3::DIST_2D) const override;
|
void getTilesInRange(FowTilesType & tiles, const int3 & pos, int radius, ETileVisibility mode, std::optional<PlayerColor> player = std::optional<PlayerColor>(), int3::EDistanceFormula formula = int3::DIST_2D) const override;
|
||||||
void getAllTiles(FowTilesType &tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter) const override;
|
void getAllTiles(FowTilesType &tiles, std::optional<PlayerColor> player, int level, const std::function<bool(const TerrainTile *)> & filter) const override;
|
||||||
|
|
||||||
|
|||||||
@@ -976,8 +976,9 @@ CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &b
|
|||||||
if (improvedNecromancy->empty())
|
if (improvedNecromancy->empty())
|
||||||
return CStackBasicDescriptor();
|
return CStackBasicDescriptor();
|
||||||
|
|
||||||
|
bool hasRaisedUnitsBonus = hasBonusOfType(BonusType::UNDEAD_RAISE_PERCENTAGE);
|
||||||
int raisedUnitsPercentage = std::clamp(valOfBonuses(BonusType::UNDEAD_RAISE_PERCENTAGE), 0, 100);
|
int raisedUnitsPercentage = std::clamp(valOfBonuses(BonusType::UNDEAD_RAISE_PERCENTAGE), 0, 100);
|
||||||
if (raisedUnitsPercentage == 0)
|
if(raisedUnitsPercentage == 0 && !hasRaisedUnitsBonus)
|
||||||
return CStackBasicDescriptor();
|
return CStackBasicDescriptor();
|
||||||
|
|
||||||
const std::map<CreatureID,si32> &casualties = battleResult.casualties[CBattleInfoEssentials::otherSide(battleResult.winner)];
|
const std::map<CreatureID,si32> &casualties = battleResult.casualties[CBattleInfoEssentials::otherSide(battleResult.winner)];
|
||||||
|
|||||||
@@ -418,7 +418,8 @@ std::vector<CIdentifierStorage::ObjectData> CIdentifierStorage::getPossibleIdent
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string fullID = request.type + '.' + request.name;
|
std::string fullID = request.type + '.' + request.name;
|
||||||
std::string fullIDCaseCorrected = request.caseSensitive ? fullID : registeredObjectsCaseLookup.at(boost::algorithm::to_lower_copy(fullID));
|
std::string fullLowerID = boost::algorithm::to_lower_copy(fullID);
|
||||||
|
std::string fullIDCaseCorrected = (request.caseSensitive || !registeredObjectsCaseLookup.count(fullLowerID)) ? fullID : registeredObjectsCaseLookup.at(fullLowerID);
|
||||||
|
|
||||||
auto entries = registeredObjects.equal_range(fullIDCaseCorrected);
|
auto entries = registeredObjects.equal_range(fullIDCaseCorrected);
|
||||||
|
|
||||||
|
|||||||
@@ -3995,7 +3995,7 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID)
|
|||||||
{
|
{
|
||||||
std::vector<int3>::iterator tile;
|
std::vector<int3>::iterator tile;
|
||||||
std::vector<int3> tiles;
|
std::vector<int3> tiles;
|
||||||
gameState().getFreeTiles(tiles);
|
gameState().getFreeTiles(tiles, true);
|
||||||
ui32 amount = tiles.size() / 200; //Chance is 0.5% for each tile
|
ui32 amount = tiles.size() / 200; //Chance is 0.5% for each tile
|
||||||
|
|
||||||
RandomGeneratorUtil::randomShuffle(tiles, getRandomGenerator());
|
RandomGeneratorUtil::randomShuffle(tiles, getRandomGenerator());
|
||||||
|
|||||||
@@ -447,52 +447,96 @@ bool BattleFlowProcessor::tryMakeAutomaticActionOfBallistaOrTowers(const CBattle
|
|||||||
if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA)
|
if ((stackCreatureId == CreatureID::ARROW_TOWERS || stackCreatureId == CreatureID::BALLISTA)
|
||||||
&& (!curOwner || !gameHandler->randomizer->rollCombatAbility(curOwner->id, curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, BonusSubtypeID(stackCreatureId)))))
|
&& (!curOwner || !gameHandler->randomizer->rollCombatAbility(curOwner->id, curOwner->valOfBonuses(BonusType::MANUAL_CONTROL, BonusSubtypeID(stackCreatureId)))))
|
||||||
{
|
{
|
||||||
|
|
||||||
BattleAction attack;
|
BattleAction attack;
|
||||||
attack.actionType = EActionType::SHOOT;
|
attack.actionType = EActionType::SHOOT;
|
||||||
attack.side = next->unitSide();
|
attack.side = next->unitSide();
|
||||||
attack.stackNumber = next->unitId();
|
attack.stackNumber = next->unitId();
|
||||||
|
|
||||||
// TODO: unify logic with AI?
|
const TStacks possibleTargets = battle.battleGetStacksIf([&next, &battle](const CStack * s)
|
||||||
// Find best target using logic similar to H3 AI
|
|
||||||
|
|
||||||
const auto & isBetterTarget = [&battle](const battle::Unit * candidate, const battle::Unit * current)
|
|
||||||
{
|
{
|
||||||
bool candidateInsideWalls = battle.battleIsInsideWalls(candidate->getPosition());
|
return s->unitOwner() != next->unitOwner() && s->isValidTarget() && battle.battleCanShoot(next, s->getPosition());
|
||||||
bool currentInsideWalls = battle.battleIsInsideWalls(current->getPosition());
|
});
|
||||||
|
|
||||||
if (candidateInsideWalls != currentInsideWalls)
|
struct TargetInfo
|
||||||
return candidateInsideWalls > currentInsideWalls;
|
{
|
||||||
|
bool insideTheWalls;
|
||||||
// also check for war machines - shooters are more dangerous than war machines, ballista or catapult
|
bool canAttackNextTurn;
|
||||||
bool candidateCanShoot = candidate->canShoot() && candidate->unitType()->warMachine == ArtifactID::NONE;
|
bool isParalyzed;
|
||||||
bool currentCanShoot = current->canShoot() && current->unitType()->warMachine == ArtifactID::NONE;
|
bool isMachine;
|
||||||
|
float towerAttackValue;
|
||||||
if (candidateCanShoot != currentCanShoot)
|
const CStack * stack;
|
||||||
return candidateCanShoot > currentCanShoot;
|
|
||||||
|
|
||||||
int64_t candidateTargetValue = static_cast<int64_t>(candidate->unitType()->getAIValue() * candidate->getCount());
|
|
||||||
int64_t currentTargetValue = static_cast<int64_t>(current->unitType()->getAIValue() * current->getCount());
|
|
||||||
|
|
||||||
return candidateTargetValue > currentTargetValue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const battle::Unit * target = nullptr;
|
const auto & getCanAttackNextTurn = [&battle] (const battle::Unit * unit)
|
||||||
|
|
||||||
for(auto & elem : battle.battleGetAllStacks(true))
|
|
||||||
{
|
{
|
||||||
if (elem->unitOwner() == next->unitOwner())
|
if (battle.battleCanShoot(unit))
|
||||||
continue;
|
return true;
|
||||||
|
|
||||||
if (!elem->isValidTarget())
|
BattleHexArray attackableHexes;
|
||||||
continue;
|
battle.battleGetAvailableHexes(unit, true, false, &attackableHexes);
|
||||||
|
return !attackableHexes.empty();
|
||||||
|
};
|
||||||
|
|
||||||
if (!battle.battleCanShoot(next, elem->getPosition()))
|
const auto & getTowerAttackValue = [&battle, &next] (const battle::Unit * unit)
|
||||||
continue;
|
{
|
||||||
|
float unitValue = static_cast<float>(unit->unitType()->getAIValue());
|
||||||
|
float singleHpValue = unitValue / static_cast<float>(unit->getMaxHealth());
|
||||||
|
float fullHp = static_cast<float>(unit->getTotalHealth());
|
||||||
|
|
||||||
if (target && !isBetterTarget(elem, target))
|
int distance = BattleHex::getDistance(next->getPosition(), unit->getPosition());
|
||||||
continue;
|
BattleAttackInfo attackInfo(next, unit, distance, true);
|
||||||
|
DamageEstimation estimation = battle.calculateDmgRange(attackInfo);
|
||||||
|
float avgDmg = (static_cast<float>(estimation.damage.max) + static_cast<float>(estimation.damage.min)) / 2;
|
||||||
|
float realAvgDmg = avgDmg > fullHp ? fullHp : avgDmg;
|
||||||
|
float avgUnitKilled = (static_cast<float>(estimation.kills.max) + static_cast<float>(estimation.kills.min)) / 2;
|
||||||
|
float dmgValue = realAvgDmg * singleHpValue;
|
||||||
|
float killValue = avgUnitKilled * unitValue;
|
||||||
|
|
||||||
target = elem;
|
return dmgValue + killValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TargetInfo>targetsInfo;
|
||||||
|
|
||||||
|
for (const CStack * possibleTarget : possibleTargets)
|
||||||
|
{
|
||||||
|
bool isMachine = possibleTarget->unitType()->warMachine != ArtifactID::NONE;
|
||||||
|
bool isParalyzed = possibleTarget->hasBonusOfType(BonusType::NOT_ACTIVE) && !isMachine;
|
||||||
|
const TargetInfo targetInfo =
|
||||||
|
{
|
||||||
|
battle.battleIsInsideWalls(possibleTarget->getPosition()),
|
||||||
|
getCanAttackNextTurn(possibleTarget),
|
||||||
|
isParalyzed,
|
||||||
|
isMachine,
|
||||||
|
getTowerAttackValue(possibleTarget),
|
||||||
|
possibleTarget
|
||||||
|
};
|
||||||
|
targetsInfo.push_back(targetInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto & isBetterTarget = [](const TargetInfo & candidate, const TargetInfo & current)
|
||||||
|
{
|
||||||
|
if (candidate.isParalyzed != current.isParalyzed)
|
||||||
|
return candidate.isParalyzed < current.isParalyzed;
|
||||||
|
|
||||||
|
if (candidate.isMachine != current.isMachine)
|
||||||
|
return candidate.isMachine < current.isMachine;
|
||||||
|
|
||||||
|
if (candidate.canAttackNextTurn != current.canAttackNextTurn)
|
||||||
|
return candidate.canAttackNextTurn > current.canAttackNextTurn;
|
||||||
|
|
||||||
|
if (candidate.insideTheWalls != current.insideTheWalls)
|
||||||
|
return candidate.insideTheWalls > current.insideTheWalls;
|
||||||
|
|
||||||
|
return candidate.towerAttackValue > current.towerAttackValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TargetInfo * target = nullptr;
|
||||||
|
|
||||||
|
for(const auto & elem : targetsInfo)
|
||||||
|
{
|
||||||
|
if (target == nullptr || isBetterTarget(elem, *target))
|
||||||
|
target = &elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(target == nullptr)
|
if(target == nullptr)
|
||||||
@@ -501,7 +545,7 @@ bool BattleFlowProcessor::tryMakeAutomaticActionOfBallistaOrTowers(const CBattle
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
attack.aimToUnit(target);
|
attack.aimToUnit(target->stack);
|
||||||
makeAutomaticAction(battle, next, attack);
|
makeAutomaticAction(battle, next, attack);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user