Merge lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands

Proposed by SirVer
Status: Rejected
Rejected by: SirVer
Proposed branch: lp:~widelands-dev/widelands/find_attack_soldiers
Merge into: lp:widelands
Diff against target: 736 lines (+206/-168)
11 files modified
src/ai/defaultai.cc (+6/-6)
src/logic/map_objects/tribes/militarysite.cc (+68/-86)
src/logic/map_objects/tribes/militarysite.h (+6/-11)
src/logic/map_objects/tribes/production_program.cc (+2/-3)
src/logic/map_objects/tribes/soldier.cc (+6/-0)
src/logic/map_objects/tribes/soldier.h (+58/-5)
src/logic/map_objects/tribes/trainingsite.cc (+2/-5)
src/logic/map_objects/tribes/warehouse.cc (+2/-3)
src/logic/player.cc (+47/-40)
src/map_io/map_buildingdata_packet.cc (+1/-1)
src/wui/soldierlist.cc (+8/-8)
To merge this branch: bzr merge lp:~widelands-dev/widelands/find_attack_soldiers
Reviewer Review Type Date Requested Status
GunChleoc Needs Fixing
SirVer Approve
TiborB Approve
Review via email: mp+245276@code.launchpad.net

Description of the change

This patch was send to me by fk who has difficulties accessing launchpad.

I did some changes to make it compile, but I did not review nor test the changes. So I do not fully know what the code actually does, but it has something to do with better selection of soldiers on attack.

To post a comment you must log in.
Revision history for this message
TiborB (tiborb95) wrote :

Just note that this is based on seafaring-ai branch. So if you merge this...

And one comment about seafaring - few days ago I left seafaring game overnight (big map, had to use speed 1x) and it crashed.

It was not under gdb and it forgot about possibility to load savegame...

So some further testing will be needed.

If somebody wants to help with this...

Revision history for this message
SirVer (sirver) wrote :

> Just note that this is based on seafaring-ai branch. So if you merge this...

My bad. Fixed by rebasing on trunk and push --overwriting.

Revision history for this message
SirVer (sirver) wrote :

I looked at this now. I think sort_soldiers should just be a std::sort with a custom comparator - the current version is fragile. That will make this patch very small.

Is somebody interested in picking up this branch? I think fk does not want to work on it anymore.

review: Needs Fixing
Revision history for this message
Martin Schmidt (martinschmidt) wrote :

Can I just push to this branch or should I use a new one?
I did not change the semnatics but rather replace the sorting by
stable_sort or partial_sort (which returns the k smallest elements but is unstable)

However, I am not sure whether an unstable sorting algorithm can cause desyncts, since the
implementation may change the result. Doing a source code search it seems that Widelands uses both, std::sort and std::stable_sort.

At the moment we need the SoldierPriority structure to distribute the attacking soldiers evenly over all military sites (in case of equal abilities)
I think the most elegant solution would be to enforce the soldiers being sorted by ability in the militarysite itself and allow a soldier to query its position.
This would allow to sort Soldiers instead of SoldierPriority and would solve all kinds of sorting problems:
- find soldiers for attacking/defending/healing (default: strongest)
- drop least suited soldier for upgrading (default: weakest)
- kicking out any soldier (by the player or for replacement/training)

And at least the above operations does not destroy the sorting.
Only new arriving soldiers would need to be inserted.
Possible replacements for std::vector<Soldier>:
- std::deque

However, this would requiere a larger modification of the source code and needs more discussing.

Revision history for this message
fk (fredkuijper) wrote :

@SirVer
Actually I am sorry for introducing this code snippet in this way, I understand that I should have done the merge, testing etcetera myself.

"sort_soldiers should just be a std::sort with a custom comparator"

Indeed, I am new to STL and haven't found a good manual about it yet. It shouldn't take that many lines as in my code.

"I think fk does not want to work on it anymore."

In a certain way you are right, I'm not really the most suitable person to do so. Actually it surprises me that no one has done this before, for an experienced programmer (with STL) it should take half an hour at most, and it is really fun to play the game this way. It is a game changer with benefits for both sides, both human and AI, that gives a more polished game experience. And even with the code in this primitive state it would already function well, I am after all an experienced programmer (but not with STL), there is not much that could go wrong and the test were promising. If I only know how to download the involved branch than I would like do the tests.

Revision history for this message
fk (fredkuijper) wrote :

@Martin
"sorted by ability in the militarysite itself"

That makes sense, for know I could say that both appproaches (mine and yours) will not collide.

Revision history for this message
Martin Schmidt (martinschmidt) wrote :

Your patch was really useful, I just changed the sorting to stl and that's it. I also tried a short game and it really makes a difference seeing soldiers coming from all military builings in range to attack instead of weaking one building totally.
Here is the patch:

=== modified file 'src/logic/player.cc'
--- src/logic/player.cc 2014-12-20 22:03:47 +0000
+++ src/logic/player.cc 2015-02-16 14:32:33 +0000
@@ -100,31 +100,26 @@
  int priority; // (strength + 1) * health - position in building.
 };

+/**
+* Find the strongest players with highest priority
+*/
+bool compare_soldiers(const SoldierPriority& a, const SoldierPriority& b) {
+ return a.priority > b.priority;
+}
+
 void sort_soldiers(std::vector<SoldierPriority>* soldiers,
- uint32_t max = std::numeric_limits<uint32_t>::max()) {
- std::vector<SoldierPriority> temp;
- SoldierPriority soldier;
- uint32_t left = soldiers->size();
-
- while (left) {
- int32_t maxlevel = -1;
- uint32_t pos = 0;
- uint32_t maxpos = 0;
- for (const SoldierPriority& soldier_priority : *soldiers) {
- const int32_t level = soldier_priority.priority;
- if (level > maxlevel) {
- maxlevel = level;
- maxpos = pos;
- soldier.soldier = soldier_priority.soldier;
- soldier.priority = level;
- }
- pos++;
- }
- temp.push_back(soldier);
- soldiers->erase(soldiers->begin() + maxpos);
- left--;
+ uint32_t max = std::numeric_limits<uint32_t>::max()) {
+ if (max >= soldiers->size()) {
+ return;
  }
- soldiers->insert(soldiers->end(), temp.begin(), temp.begin() + std::min<int>(max, temp.size()));
+
+ std::stable_sort(soldiers->begin(), soldiers->end(), compare_soldiers);
+ // Can an unstable sorting algorithm cause desyncts or are the implementations
+ // of std algorithms guaranteed to return the same (possibly non-stable)
+ // sorting? If yes, partial_sort may be favourable
+ // std::partial_sort(soldiers->begin(), soldiers->begin() + max,
+ // soldiers->end(), compare_soldiers);
+ soldiers->resize(max);
 }

 }

Revision history for this message
SirVer (sirver) wrote :

Mars, could you just push and reuse this branch please?

On my phone, will have a closer look from home or tomorrow.

> Am 16.02.2015 um 15:33 schrieb Martin Schmidt <email address hidden>:
>
> Your patch was really useful, I just changed the sorting to stl and that's it. I also tried a short game and it really makes a difference seeing soldiers coming from all military builings in range to attack instead of weaking one building totally.
> Here is the patch:
>
> === modified file 'src/logic/player.cc'
> --- src/logic/player.cc 2014-12-20 22:03:47 +0000
> +++ src/logic/player.cc 2015-02-16 14:32:33 +0000
> @@ -100,31 +100,26 @@
> int priority; // (strength + 1) * health - position in building.
> };
>
> +/**
> +* Find the strongest players with highest priority
> +*/
> +bool compare_soldiers(const SoldierPriority& a, const SoldierPriority& b) {
> + return a.priority > b.priority;
> +}
> +
> void sort_soldiers(std::vector<SoldierPriority>* soldiers,
> - uint32_t max = std::numeric_limits<uint32_t>::max()) {
> - std::vector<SoldierPriority> temp;
> - SoldierPriority soldier;
> - uint32_t left = soldiers->size();
> -
> - while (left) {
> - int32_t maxlevel = -1;
> - uint32_t pos = 0;
> - uint32_t maxpos = 0;
> - for (const SoldierPriority& soldier_priority : *soldiers) {
> - const int32_t level = soldier_priority.priority;
> - if (level > maxlevel) {
> - maxlevel = level;
> - maxpos = pos;
> - soldier.soldier = soldier_priority.soldier;
> - soldier.priority = level;
> - }
> - pos++;
> - }
> - temp.push_back(soldier);
> - soldiers->erase(soldiers->begin() + maxpos);
> - left--;
> + uint32_t max = std::numeric_limits<uint32_t>::max()) {
> + if (max >= soldiers->size()) {
> + return;
> }
> - soldiers->insert(soldiers->end(), temp.begin(), temp.begin() + std::min<int>(max, temp.size()));
> +
> + std::stable_sort(soldiers->begin(), soldiers->end(), compare_soldiers);
> + // Can an unstable sorting algorithm cause desyncts or are the implementations
> + // of std algorithms guaranteed to return the same (possibly non-stable)
> + // sorting? If yes, partial_sort may be favourable
> + // std::partial_sort(soldiers->begin(), soldiers->begin() + max,
> + // soldiers->end(), compare_soldiers);
> + soldiers->resize(max);
> }
>
> }
>
>
> --
> https://code.launchpad.net/~widelands-dev/widelands/find_attack_soldiers/+merge/245276
> You proposed lp:~widelands-dev/widelands/find_attack_soldiers for merging.

Revision history for this message
Martin Schmidt (martinschmidt) wrote :

What is the best way to proceed for establishing the invariant that soldiers in the military building have to be sorted:
- create a bug report / wishlist,
- create a blue print or
- start coding and propose a merge request ?

Revision history for this message
TiborB (tiborb95) wrote :

Generally:

- create a bug report (optional but I recommend this :) )
- start coding and create own branch
- propose merging

2015-02-18 18:09 GMT+01:00 Martin Schmidt <email address hidden>:

> What is the best way to proceed for establishing the invariant that
> soldiers in the military building have to be sorted:
> - create a bug report / wishlist,
> - create a blue print or
> - start coding and propose a merge request ?
> --
>
> https://code.launchpad.net/~widelands-dev/widelands/find_attack_soldiers/+merge/245276
> Your team Widelands Developers is subscribed to branch
> lp:~widelands-dev/widelands/find_attack_soldiers.
>
> _______________________________________________
> Mailing list: https://launchpad.net/~widelands-dev
> Post to : <email address hidden>
> Unsubscribe : https://launchpad.net/~widelands-dev
> More help : https://help.launchpad.net/ListHelp
>

Revision history for this message
SirVer (sirver) wrote :

martin, please ping this merge request when you feel it is ready. Launchpad is not sending notifications for new commits.

Revision history for this message
Martin Schmidt (martinschmidt) wrote :

ok, basic functionality is implemented, but still, a few things have to be done, e.g.
- update military site window when the user changes the soldier preference
(at the moment you have to close and open the window again to see the soldiers in the right ordering)
- make sure healthy soldiers defend when the preference is set for rookies (instead of wounded ones who have lower power and are therefore prefered)

nice to have: when upgrading soldiers/occupying a building send the best soldiers available (according to the preference set)

keeping the soldiers sorted permanently is more difficult than I expected, the soldiercontrol interface does not offer a unified way of storing them, so for military sites they are upcasted from workers each time.

Revision history for this message
SirVer (sirver) wrote :

> - update military site window when the user changes the soldier preference
(at the moment you have to close and open the window again to see the soldiers in the right ordering)

use a Boost::signal for that - i.e. the militarysite has one, and the ui can connect to it to update it whenever something is changed.

> - make sure healthy soldiers defend when the preference is set for rookies (instead of wounded ones who have lower power and are therefore prefered)

How do you want to deal with that? It sounds like you want a different sorting for this.

> nice to have: when upgrading soldiers/occupying a building send the best soldiers available (according to the preference set)

Actually that has been discussed before. It makes for an interesting strategic element when new buildings are not immediately manned with an invincible army. It gives the other player an opportunity to strike. I think this is cool and more dynamic than immediately having the best soldiers at the front.

> keeping the soldiers sorted permanently is more difficult than I expected, the soldiercontrol interface does not offer a unified way of storing them, so for military sites they are upcasted from workers each time.

Why not change that? Remove the soldier control interface and instead make a HasSoldier container that can be owned by a building and handles all soldiers in the building. This gets rid of some inheritance which is usually bad and will solve your case. Just a suggestion of course.

Is this branch ready for review/merging now or do you still want to work on it?

Revision history for this message
TiborB (tiborb95) wrote :

I second SirVer's question: Is this branch ready for review/merging now or do you still want to work on it?

It seems there is some work to do here yet.

Revision history for this message
Martin Schmidt (martinschmidt) wrote :

I guess it is ready to merge.
What is implemented:
 - defend with "strongest", attack with "strongest"
 - drop "weakest", but only unwounded soldiers (because wounded soldiers will heal)
I am quite happy with how it works and feels playing with this branch.

What is missing:
 - the list of soldiers in a militarysite is not updated until the windows of the militarysite is
   closed and opened again (this is neccessary when new soldiers arrive, soldiers return from battle,
   soldiers are drop or the priority (weakest/strongest) for a militarysite is changed.
I tried to implement this, however I have no idea how to do this and give up on this.
But you are right, I should have responeded earlier.

"weak" and "strong" refers to the chosen priority.
For priority weak, weak soldiers are "strong".

Revision history for this message
TiborB (tiborb95) wrote :

" the list of soldiers in a militarysite is not updated until the windows of the militarysite is..." - this would be very useful, but personally I am not able to help with this :(

Revision history for this message
GunChleoc (gunchleoc) wrote :

Can you create a bug report for this, so we can take care of it in a separate branch? I have worked with signals before on the UI and might be able to figure it out, so I would put it on my todo-list.

Revision history for this message
Martin Schmidt (martinschmidt) wrote :

I will create a bug report as soon as this branch is merged.

Revision history for this message
TiborB (tiborb95) wrote :

I looked at the code and run some AI-only game - no problems. I think it can go.

review: Approve
Revision history for this message
TiborB (tiborb95) wrote :

Is this waiting only for a volunteer who would merge it to trunk? I can do it....

Revision history for this message
GunChleoc (gunchleoc) wrote :

We still have a reviewer saying "Needs fixing", which is why I didn't merge it yet. Let's wait for SirVer to get back.

Revision history for this message
SirVer (sirver) wrote :

sorry - did not see that.

review: Approve
Revision history for this message
GunChleoc (gunchleoc) wrote :

I just found that this breaks the regression tests. The problem is with test/maps/lua_testsuite.wmf/scripting/test_lua_in_game.lua. The failing test is function militarysite_tests:test_no_space().

review: Needs Fixing
Revision history for this message
Martin Schmidt (martinschmidt) wrote :

ok, thanks, I will look into this

Revision history for this message
bunnybot (widelandsofficial) wrote :

Hi, I am bunnybot (https://github.com/widelands/bunnybot).

I am keeping the source branch lp:~widelands-dev/widelands/find_attack_soldiers mirrored to
  https://github.com/widelands/widelands/tree/_widelands_dev_widelands_find_attack_soldiers

The latest continuous integration build can always be found here:
  https://travis-ci.org/widelands/widelands/branches
Please do not merge without making sure that it passes.

You can give me commands by starting a line with @bunnybot <command>. I understand:
 merge: Merges the source branch into the target branch, closing the pull request.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Travis build 126 has changed state to: failed. Details: https://travis-ci.org/widelands/widelands/builds/99797556.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Travis build 126 has changed state to: failed. Details: https://travis-ci.org/widelands/widelands/builds/99797556.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Travis build 126 has changed state to: failed. Details: https://travis-ci.org/widelands/widelands/builds/99797556.

Revision history for this message
SirVer (sirver) wrote :

I do not feel like going over this and clean it up. I do not understand most of the intentions.

Martin, are you still interested to work on this? Otherwise I will drop this branch in a few days.

Revision history for this message
bunnybot (widelandsofficial) wrote :

Bunnybot encountered an error while working on this merge proposal:

<urlopen error [Errno -2] Name or service not known>

Revision history for this message
bunnybot (widelandsofficial) wrote :

Bunnybot encountered an error while working on this merge proposal:

<urlopen error [Errno -2] Name or service not known>

Revision history for this message
bunnybot (widelandsofficial) wrote :

Bunnybot encountered an error while working on this merge proposal:

<urlopen error [Errno -2] Name or service not known>

Revision history for this message
bunnybot (widelandsofficial) wrote :

Bunnybot encountered an error while working on this merge proposal:

<urlopen error [Errno -2] Name or service not known>

Revision history for this message
Martin Schmidt (martinschmidt) wrote :

> I do not feel like going over this and clean it up. I do not understand most
> of the intentions.
>
> Martin, are you still interested to work on this? Otherwise I will drop this
> branch in a few days.

You can delete this branch, I had too much trouble with it.

Revision history for this message
SirVer (sirver) wrote :

Abandoning this then.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/ai/defaultai.cc'
2--- src/ai/defaultai.cc 2016-01-18 19:35:25 +0000
3+++ src/ai/defaultai.cc 2016-01-24 08:32:15 +0000
4@@ -4342,9 +4342,9 @@
5 if (ms->economy().warehouses().size()) {
6 uint32_t const j = ms->soldier_capacity();
7
8- if (MilitarySite::kPrefersRookies != ms->get_soldier_preference()) {
9+ if (Soldier::kPrefersRookies != ms->get_soldier_preference()) {
10 game().send_player_militarysite_set_soldier_preference(
11- *ms, MilitarySite::kPrefersRookies);
12+ *ms, Soldier::kPrefersRookies);
13 } else if (j > 1) {
14 game().send_player_change_soldier_capacity(*ms, (j > 2) ? -2 : -1);
15 }
16@@ -4400,9 +4400,9 @@
17 changed = true;
18
19 // and also set preference to Heroes
20- if (MilitarySite::kPrefersHeroes != ms->get_soldier_preference()) {
21+ if (Soldier::kPrefersHeroes != ms->get_soldier_preference()) {
22 game().send_player_militarysite_set_soldier_preference(
23- *ms, MilitarySite::kPrefersHeroes);
24+ *ms, Soldier::kPrefersHeroes);
25 changed = true;
26 }
27
28@@ -4411,9 +4411,9 @@
29 } else { // otherwise decrease soldiers
30 uint32_t const j = ms->soldier_capacity();
31
32- if (MilitarySite::kPrefersRookies != ms->get_soldier_preference()) {
33+ if (Soldier::kPrefersRookies != ms->get_soldier_preference()) {
34 game().send_player_militarysite_set_soldier_preference(
35- *ms, MilitarySite::kPrefersRookies);
36+ *ms, Soldier::kPrefersRookies);
37 } else if (j > 1) {
38 game().send_player_change_soldier_capacity(*ms, (j > 2) ? -2 : -1);
39 }
40
41=== modified file 'src/logic/map_objects/tribes/militarysite.cc'
42--- src/logic/map_objects/tribes/militarysite.cc 2016-01-06 19:11:20 +0000
43+++ src/logic/map_objects/tribes/militarysite.cc 2016-01-24 08:32:15 +0000
44@@ -19,6 +19,7 @@
45
46 #include "logic/map_objects/tribes/militarysite.h"
47
48+#include <algorithm>
49 #include <clocale>
50 #include <cstdio>
51 #include <memory>
52@@ -88,26 +89,22 @@
53 =============================
54 */
55
56-MilitarySite::MilitarySite(const MilitarySiteDescr & ms_descr) :
57+MilitarySite::MilitarySite(const MilitarySiteDescr& ms_descr) :
58 Building(ms_descr),
59-m_didconquer (false),
60-m_capacity (ms_descr.get_max_number_of_soldiers()),
61-m_nexthealtime(0),
62-m_soldier_preference(ms_descr.m_prefers_heroes_at_start ? kPrefersHeroes : kPrefersRookies),
63+m_didconquer(false),
64+m_capacity(ms_descr.get_max_number_of_soldiers()),
65+m_soldier_preference(ms_descr.m_prefers_heroes_at_start ? Soldier::kPrefersHeroes : Soldier::kPrefersRookies),
66+m_next_swap_soldiers_time(0),
67 m_soldier_upgrade_try(false),
68 m_doing_upgrade_request(false)
69 {
70- m_next_swap_soldiers_time = 0;
71 }
72
73-
74-MilitarySite::~MilitarySite()
75-{
76+MilitarySite::~MilitarySite() {
77 assert(!m_normal_soldier_request);
78 assert(!m_upgrade_soldier_request);
79 }
80
81-
82 /**
83 ===============
84 Display number of soldiers.
85@@ -275,34 +272,33 @@
86 return 0;
87 }
88
89-
90 /*
91- * Returns the least wanted soldier -- If player prefers zero-level guys,
92- * the most trained soldier is the "weakest guy".
93+ * Returns the least wanted soldier that is fully healed
94+ * Wounded soldiers are ignored since they are supposed to be healed
95+ * If player prefers rookies guys, the most trained soldier is the "weakest guy".
96 */
97-
98-Soldier *
99-MilitarySite::find_least_suited_soldier()
100-{
101- const std::vector<Soldier *> present = present_soldiers();
102- const int32_t multiplier = kPrefersHeroes == m_soldier_preference ? -1 : 1;
103- int worst_soldier_level = INT_MIN;
104- Soldier * worst_soldier = nullptr;
105- for (Soldier* sld : present) {
106- int this_soldier_level = multiplier * static_cast<int> (sld->get_level(atrTotal));
107- if (this_soldier_level > worst_soldier_level)
108- {
109- worst_soldier_level = this_soldier_level;
110- worst_soldier = sld;
111+Soldier* MilitarySite::find_least_suited_soldier() {
112+
113+ const std::vector<Soldier*> present = present_soldiers();
114+ if (present.empty()) {
115+ return nullptr;
116+ }
117+
118+ for (auto rit = present.rbegin(); rit != present.rend(); ++rit) {
119+ Soldier* s = (*rit);
120+ if (s->get_current_hitpoints() == s->get_max_hitpoints()) {
121+ return s;
122 }
123 }
124- return worst_soldier;
125+
126+ // if no fully healed soldier is found, return the last soldier
127+ // this is the most wounded one
128+ return present.back();
129 }
130
131-
132 /*
133- * Kicks out the least wanted soldier -- If player prefers zero-level guys,
134- * the most trained soldier is the "weakest guy".
135+ * Kicks out the least wanted soldier -- If player prefers rookies,
136+ * the most trained soldier is the least suited guy.
137 *
138 * Returns false, if there is only one soldier (won't drop last soldier)
139 * or the new guy is not better than the weakest one present, in which case
140@@ -314,40 +310,26 @@
141 * arrives.
142 */
143
144-bool
145-MilitarySite::drop_least_suited_soldier(bool new_soldier_has_arrived, Soldier * newguy)
146-{
147- const std::vector<Soldier *> present = present_soldiers();
148-
149- // If I have only one soldier, and the new guy is not here yet, I can't release.
150- if (new_soldier_has_arrived || 1 < present.size())
151- {
152- Soldier * kickoutCandidate = find_least_suited_soldier();
153-
154- // If the arriving guy is worse than worst present, I wont't release.
155- if (nullptr != newguy && nullptr != kickoutCandidate)
156- {
157- int32_t old_level = kickoutCandidate->get_level(atrTotal);
158- int32_t new_level = newguy->get_level(atrTotal);
159- if (kPrefersHeroes == m_soldier_preference && old_level >= new_level)
160- {
161- return false;
162- }
163- else
164- if (kPrefersRookies == m_soldier_preference && old_level <= new_level)
165- {
166- return false;
167- }
168- }
169-
170- // Now I know that the new guy is worthy.
171- if (nullptr != kickoutCandidate)
172- {
173- Game & game = dynamic_cast<Game&>(owner().egbase());
174- kickoutCandidate->reset_tasks(game);
175- kickoutCandidate->start_task_leavebuilding(game, true);
176- return true;
177- }
178+bool MilitarySite::drop_least_suited_soldier(bool new_soldier_has_arrived, Soldier* newguy) {
179+
180+ std::vector<Soldier*> present = present_soldiers();
181+
182+ // respect the minimal soldier capacity
183+ if (!new_soldier_has_arrived && present.size() <= min_soldier_capacity()) {
184+ return false;
185+ }
186+
187+ Soldier* kickoutCandidate = find_least_suited_soldier();
188+
189+ // If the arriving guy is worse than worst present, do not drop a soldier
190+ if (kickoutCandidate != nullptr &&
191+ (newguy == nullptr ||
192+ Soldier::CompareSoldier(m_soldier_preference)(newguy, kickoutCandidate))) {
193+
194+ Game& game = dynamic_cast<Game&>(owner().egbase());
195+ kickoutCandidate->reset_tasks(game);
196+ kickoutCandidate->start_task_leavebuilding(game, true);
197+ return true;
198 }
199 return false;
200 }
201@@ -677,7 +659,6 @@
202 return false;
203 }
204
205-
206 /**
207 * \return \c true if the soldier is currently present and idle in the building.
208 */
209@@ -689,7 +670,6 @@
210 soldier.get_position() == get_position();
211 }
212
213-// TODO(sirver): This method should probably return a const reference.
214 std::vector<Soldier *> MilitarySite::present_soldiers() const
215 {
216 std::vector<Soldier *> soldiers;
217@@ -701,10 +681,11 @@
218 }
219 }
220 }
221+
222+ std::sort(soldiers.begin(), soldiers.end(), Soldier::CompareSoldier(m_soldier_preference));
223 return soldiers;
224 }
225
226-// TODO(sirver): This method should probably return a const reference.
227 std::vector<Soldier *> MilitarySite::stationed_soldiers() const
228 {
229 std::vector<Soldier *> soldiers;
230@@ -934,7 +915,7 @@
231
232 // feature request 1247384 in launchpad bugs: Conquered buildings tend to
233 // be in a hostile area; typically players want heroes there.
234- set_soldier_preference(kPrefersHeroes);
235+ set_soldier_preference(Soldier::kPrefersHeroes);
236 }
237
238 /// Calculates whether the military presence is still kept and \returns true if.
239@@ -1056,31 +1037,32 @@
240 * The routine returns true if upgrade request thresholds have changed. This information could be
241 * used to decide whether the soldier-Request should be upgraded.
242 */
243-bool
244-MilitarySite::update_upgrade_requirements()
245-{
246+bool MilitarySite::update_upgrade_requirements() {
247 int32_t soldier_upgrade_required_min = m_soldier_upgrade_requirements.get_min();
248 int32_t soldier_upgrade_required_max = m_soldier_upgrade_requirements.get_max();
249
250- if (kPrefersHeroes != m_soldier_preference && kPrefersRookies != m_soldier_preference)
251- {
252- log("MilitarySite::swapSoldiers: error: Unknown player preference %d.\n", m_soldier_preference);
253+ if (Soldier::kPrefersHeroes != m_soldier_preference &&
254+ Soldier::kPrefersRookies != m_soldier_preference) {
255+ log("MilitarySite::swapSoldiers: error: Unknown player preference %d.\n",
256+ m_soldier_preference);
257 m_soldier_upgrade_try = false;
258 return false;
259 }
260
261- // Find the level of the soldier that is currently least-suited.
262- Soldier * worst_guy = find_least_suited_soldier();
263+ // make sure worst_guy is defined
264+ Soldier* worst_guy = find_least_suited_soldier();
265+
266 if (worst_guy == nullptr) {
267- // There could be no soldier in the militarysite right now. No reason to freak out.
268+ // There could be no soldier in the militarysite right now
269 return false;
270 }
271+
272 int32_t wg_level = worst_guy->get_level(atrTotal);
273
274 // Micro-optimization: I assume that the majority of military sites have only level-zero
275 // soldiers and prefer rookies. Handle them separately.
276 m_soldier_upgrade_try = true;
277- if (kPrefersRookies == m_soldier_preference) {
278+ if (Soldier::kPrefersRookies == m_soldier_preference) {
279 if (0 == wg_level)
280 {
281 m_soldier_upgrade_try = false;
282@@ -1089,8 +1071,10 @@
283 }
284
285 // Now I actually build the new requirements.
286- int32_t reqmin = kPrefersHeroes == m_soldier_preference ? 1 + wg_level : 0;
287- int32_t reqmax = kPrefersHeroes == m_soldier_preference ? SHRT_MAX : wg_level - 1;
288+ int32_t reqmin =
289+ Soldier::kPrefersHeroes == m_soldier_preference ? 1 + wg_level : 0;
290+ int32_t reqmax =
291+ Soldier::kPrefersHeroes == m_soldier_preference ? SHRT_MAX : wg_level - 1;
292
293 bool maxchanged = reqmax != soldier_upgrade_required_max;
294 bool minchanged = reqmin != soldier_upgrade_required_min;
295@@ -1111,12 +1095,10 @@
296
297 // setters
298
299-void
300-MilitarySite::set_soldier_preference(MilitarySite::SoldierPreference p)
301-{
302- assert(kPrefersHeroes == p || kPrefersRookies == p);
303+void MilitarySite::set_soldier_preference(Soldier::SoldierPreference p) {
304+ assert(Soldier::kPrefersHeroes == p ||
305+ Soldier::kPrefersRookies == p);
306 m_soldier_preference = p;
307 m_next_swap_soldiers_time = 0;
308 }
309-
310 }
311
312=== modified file 'src/logic/map_objects/tribes/militarysite.h'
313--- src/logic/map_objects/tribes/militarysite.h 2016-01-06 19:11:20 +0000
314+++ src/logic/map_objects/tribes/militarysite.h 2016-01-24 08:32:15 +0000
315@@ -27,6 +27,7 @@
316 #include "logic/map_objects/attackable.h"
317 #include "logic/map_objects/tribes/building.h"
318 #include "logic/map_objects/tribes/requirements.h"
319+#include "logic/map_objects/tribes/soldier.h"
320 #include "logic/map_objects/tribes/soldiercontrol.h"
321 #include "scripting/lua_table.h"
322
323@@ -34,6 +35,7 @@
324
325 class Soldier;
326 class World;
327+class MilitarySite;
328
329 class MilitarySiteDescr : public BuildingDescr {
330 public:
331@@ -72,13 +74,6 @@
332 MO_DESCR(MilitarySiteDescr)
333
334 public:
335- // I assume elsewhere, that enum SoldierPreference fits to uint8_t.
336- enum SoldierPreference : uint8_t {
337- kNoPreference,
338- kPrefersRookies,
339- kPrefersHeroes,
340- };
341-
342 MilitarySite(const MilitarySiteDescr &);
343 virtual ~MilitarySite();
344
345@@ -122,8 +117,8 @@
346
347 void update_soldier_request(bool i = false);
348
349- void set_soldier_preference(SoldierPreference);
350- SoldierPreference get_soldier_preference() const {
351+ void set_soldier_preference(Soldier::SoldierPreference);
352+ Soldier::SoldierPreference get_soldier_preference() const {
353 return m_soldier_preference;
354 }
355
356@@ -149,7 +144,7 @@
357 void update_normal_soldier_request();
358 void update_upgrade_soldier_request();
359 bool incorporate_upgraded_soldier(EditorGameBase & game, Soldier & s);
360- Soldier * find_least_suited_soldier();
361+ Soldier* find_least_suited_soldier();
362 bool drop_least_suited_soldier(bool new_has_arrived, Soldier * s);
363
364
365@@ -172,7 +167,7 @@
366 bool stayhome;
367 };
368 std::vector<SoldierJob> m_soldierjobs;
369- SoldierPreference m_soldier_preference;
370+ Soldier::SoldierPreference m_soldier_preference;
371 int32_t m_next_swap_soldiers_time;
372 bool m_soldier_upgrade_try; // optimization -- if everybody is zero-level, do not downgrade
373 bool m_doing_upgrade_request;
374
375=== modified file 'src/logic/map_objects/tribes/production_program.cc'
376--- src/logic/map_objects/tribes/production_program.cc 2016-01-24 07:45:11 +0000
377+++ src/logic/map_objects/tribes/production_program.cc 2016-01-24 08:32:15 +0000
378@@ -1467,9 +1467,8 @@
379 (Game & game, ProductionSite & ps) const
380 {
381 SoldierControl & ctrl = dynamic_cast<SoldierControl &>(ps);
382- const std::vector<Soldier *> soldiers = ctrl.present_soldiers();
383- const std::vector<Soldier *>::const_iterator soldiers_end =
384- soldiers.end();
385+ const std::vector<Soldier*> soldiers = ctrl.present_soldiers();
386+ const std::vector<Soldier*>::const_iterator soldiers_end = soldiers.end();
387 std::vector<Soldier *>::const_iterator it = soldiers.begin();
388
389 ps.molog
390
391=== modified file 'src/logic/map_objects/tribes/soldier.cc'
392--- src/logic/map_objects/tribes/soldier.cc 2016-01-18 05:12:51 +0000
393+++ src/logic/map_objects/tribes/soldier.cc 2016-01-24 08:32:15 +0000
394@@ -1605,6 +1605,12 @@
395 schedule_destroy(game);
396 }
397
398+float Soldier::compute_power() const {
399+ // (MaxAttack+MinAttack)/2)*(100/(100-Defense))*(100/(100-Evade))*CurHP
400+ return ((get_max_attack() + get_max_attack()) / 2.0) * (100.0 / (100.0 - get_defense())) *
401+ (100.0 / (100.0 - get_evade())) * get_current_hitpoints();
402+}
403+
404 /**
405 * Accept a Bob if it is a Soldier, is not dead and is running taskAttack or
406 * taskDefense.
407
408=== modified file 'src/logic/map_objects/tribes/soldier.h'
409--- src/logic/map_objects/tribes/soldier.h 2016-01-06 19:11:20 +0000
410+++ src/logic/map_objects/tribes/soldier.h 2016-01-24 08:32:15 +0000
411@@ -30,10 +30,6 @@
412
413 namespace Widelands {
414
415-// Constants used to launch attacks
416-#define WEAKEST 0
417-#define STRONGEST 1
418-
419 class EditorGameBase;
420 class Battle;
421
422@@ -159,7 +155,63 @@
423 MO_DESCR(SoldierDescr)
424
425 public:
426- Soldier(const SoldierDescr &);
427+ Soldier(const SoldierDescr&);
428+
429+ enum SoldierPreference : uint8_t {
430+ kNoPreference, // useful if no ordering is prefered -> ordered by serial
431+ kPrefersRookies,
432+ kPrefersHeroes,
433+ };
434+
435+ struct CompareSoldier {
436+
437+ CompareSoldier(const SoldierPreference preference = kPrefersHeroes)
438+ : m_preference(preference) {
439+ }
440+
441+ bool operator()(const Soldier* s, const Soldier* t) {
442+
443+ Serial serial_s = s->serial();
444+ Serial serial_t = t->serial();
445+
446+ if (m_preference == kNoPreference) {
447+ return s->serial() < t->serial();
448+ }
449+
450+ // independent of preference, prefer healthy soldiers
451+ // percentage of health s > percentage of health t
452+ // a1 / a2 > b1 / b2 iff a1*b2 > b1*a2
453+ uint32_t health_s = s->get_current_hitpoints() * t->get_max_hitpoints();
454+ uint32_t health_t = t->get_current_hitpoints() * s->get_max_hitpoints();
455+
456+ if (health_s > health_t) {
457+ return true;
458+ }
459+ if (health_t > health_s) {
460+ return false;
461+ }
462+
463+ // the serial is added to have some "randomness" in the ordering
464+ // in case of equal power, they seem to be selected at random
465+ // effect: attacking soldiers are distributed to military sites in range
466+ float power_s = s->compute_power() + (serial_s % 5);
467+ float power_t = t->compute_power() + (serial_t % 5);
468+
469+ if (power_s - power_t < 1e-5) {
470+ return m_preference == kPrefersRookies;
471+ }
472+
473+ if (power_t - power_s < 1e-5) {
474+ return m_preference == kPrefersHeroes;
475+ }
476+
477+ return s->serial() < t->serial();
478+ }
479+
480+ private:
481+ const SoldierPreference m_preference;
482+ };
483+
484
485 void init(EditorGameBase &) override;
486 void cleanup(EditorGameBase &) override;
487@@ -245,6 +297,7 @@
488 void move_in_battle_update(Game &, State &);
489 void die_update(Game &, State &);
490 void die_pop(Game &, State &);
491+ float compute_power() const;
492
493 void send_space_signals(Game &);
494 bool stay_home();
495
496=== modified file 'src/logic/map_objects/tribes/trainingsite.cc'
497--- src/logic/map_objects/tribes/trainingsite.cc 2016-01-18 19:35:25 +0000
498+++ src/logic/map_objects/tribes/trainingsite.cc 2016-01-24 08:32:15 +0000
499@@ -408,14 +408,11 @@
500 return 0;
501 }
502
503-
504-std::vector<Soldier *> TrainingSite::present_soldiers() const
505-{
506+std::vector<Soldier*> TrainingSite::present_soldiers() const {
507 return m_soldiers;
508 }
509
510-std::vector<Soldier *> TrainingSite::stationed_soldiers() const
511-{
512+std::vector<Soldier*> TrainingSite::stationed_soldiers() const {
513 return m_soldiers;
514 }
515
516
517=== modified file 'src/logic/map_objects/tribes/warehouse.cc'
518--- src/logic/map_objects/tribes/warehouse.cc 2016-01-08 21:00:39 +0000
519+++ src/logic/map_objects/tribes/warehouse.cc 2016-01-24 08:32:15 +0000
520@@ -1435,9 +1435,8 @@
521 /*
522 * SoldierControl implementations
523 */
524-std::vector<Soldier *> Warehouse::present_soldiers() const
525-{
526- std::vector<Soldier *> rv;
527+std::vector<Soldier*> Warehouse::present_soldiers() const {
528+ std::vector<Soldier*> rv;
529
530 DescriptionIndex const soldier_index = owner().tribe().soldier();
531 IncorporatedWorkers::const_iterator sidx = m_incorporated_workers.find(soldier_index);
532
533=== modified file 'src/logic/player.cc'
534--- src/logic/player.cc 2016-01-17 09:54:31 +0000
535+++ src/logic/player.cc 2016-01-24 08:32:15 +0000
536@@ -19,6 +19,7 @@
537
538 #include "logic/player.h"
539
540+#include <algorithm>
541 #include <memory>
542
543 #include <boost/bind.hpp>
544@@ -54,6 +55,7 @@
545 #include "sound/sound_handler.h"
546 #include "wui/interactive_player.h"
547
548+namespace Widelands {
549
550 namespace {
551
552@@ -88,12 +90,8 @@
553 }
554 }
555
556-
557-
558 }
559
560-namespace Widelands {
561-
562 const RGBColor Player::Colors[MAX_PLAYERS] = {
563 RGBColor(2, 2, 198), // blue
564 RGBColor(255, 41, 0), // red
565@@ -717,7 +715,7 @@
566 {
567 if (&imm.owner() == this)
568 if (upcast(MilitarySite, milsite, &imm))
569- milsite->set_soldier_preference(static_cast<MilitarySite::SoldierPreference>(m_soldier_preference));
570+ milsite->set_soldier_preference(static_cast<Soldier::SoldierPreference>(m_soldier_preference));
571 }
572
573
574@@ -891,57 +889,66 @@
575 */
576
577 /**
578- * Get a list of soldiers that this player can use to attack the
579- * building at the given flag.
580- *
581- * The default attack should just take the first N soldiers of the
582- * returned array.
583- */
584-// TODO(unknown): Perform a meaningful sort on the soldiers array.
585-uint32_t Player::find_attack_soldiers
586- (Flag & flag, std::vector<Soldier *> * soldiers, uint32_t nr_wanted)
587-{
588- uint32_t count = 0;
589+* Return the number of soldiers that can attack the building at the
590+* given flag, limited to \param nr_wanted.
591+*
592+* If \param soldiers != NULL it will be filled with soldiers selected
593+* on health and strength while trying to select the soldiers from
594+* multiple buildings.
595+*/
596+uint32_t
597+Player::find_attack_soldiers(Flag& flag, std::vector<Soldier*>* soldiers, uint32_t nr_wanted) {
598+ std::vector<Soldier*> attackers_all;
599+ uint32_t nr_available = 0;
600
601 if (soldiers)
602 soldiers->clear();
603
604- Map & map = egbase().map();
605- std::vector<BaseImmovable *> flags;
606+ Map& map = egbase().map();
607+ std::vector<BaseImmovable*> flags;
608
609- map.find_reachable_immovables_unique
610- (Area<FCoords>(map.get_fcoords(flag.get_position()), 25),
611- flags,
612- CheckStepDefault(MOVECAPS_WALK),
613- FindFlagOf(FindImmovablePlayerMilitarySite(*this)));
614+ map.find_reachable_immovables_unique(Area<FCoords>(map.get_fcoords(flag.get_position()), 25),
615+ flags,
616+ CheckStepDefault(MOVECAPS_WALK),
617+ FindFlagOf(FindImmovablePlayerMilitarySite(*this)));
618
619 if (flags.empty())
620 return 0;
621
622- for (BaseImmovable * temp_flag : flags) {
623- upcast(Flag, attackerflag, temp_flag);
624- upcast(MilitarySite, ms, attackerflag->get_building());
625- std::vector<Soldier *> const present = ms->present_soldiers();
626- uint32_t const nr_staying = ms->min_soldier_capacity();
627- uint32_t const nr_present = present.size();
628+ // Count (and if necessary collect) available soldiers.
629+ for (const BaseImmovable* base_immovable : flags) {
630+ const Flag* attackerflag = static_cast<const Flag*>(base_immovable);
631+ const MilitarySite* ms = static_cast<MilitarySite*>(attackerflag->get_building());
632+ std::vector<Soldier*> present = ms->present_soldiers();
633+ const uint32_t nr_staying = ms->min_soldier_capacity();
634+ const uint32_t nr_present = present.size();
635 if (nr_staying < nr_present) {
636- uint32_t const nr_taken =
637- std::min(nr_wanted, nr_present - nr_staying);
638- if (soldiers)
639- soldiers->insert
640- (soldiers->end(),
641- present.begin(), present.begin() + nr_taken);
642- count += nr_taken;
643- nr_wanted -= nr_taken;
644- if (!nr_wanted)
645- break;
646+ const uint32_t nr_taken = nr_present - nr_staying;
647+
648+ if (soldiers) {
649+ std::copy_n(present.begin(), nr_taken, std::back_inserter(attackers_all));
650+ }
651+
652+ nr_available += nr_taken;
653 }
654 }
655+ const uint32_t count = std::min(nr_available, nr_wanted);
656+ if (soldiers) {
657+ // Sort soldiers and remove surplus.
658+ soldiers->reserve(count);
659+ std::vector<Soldier*>::iterator pos = std::next(attackers_all.begin(), count);
660+ // Even if weak soldiers are prefered, strong soldiers should attack
661+ // otherwise, wounded soldiers would be send first
662+ std::partial_sort(attackers_all.begin(),
663+ pos,
664+ attackers_all.end(),
665+ Soldier::CompareSoldier(Soldier::SoldierPreference::kPrefersHeroes));
666+ std::copy(attackers_all.begin(), pos, std::back_inserter(*soldiers));
667+ }
668
669 return count;
670 }
671
672-
673 // TODO(unknown): Clean this mess up. The only action we really have right now is
674 // to attack, so pretending we have more types is pointless.
675 void Player::enemyflagaction
676
677=== modified file 'src/map_io/map_buildingdata_packet.cc'
678--- src/map_io/map_buildingdata_packet.cc 2016-01-18 19:35:25 +0000
679+++ src/map_io/map_buildingdata_packet.cc 2016-01-24 08:32:15 +0000
680@@ -563,7 +563,7 @@
681 uint16_t reqmin = fr.unsigned_16();
682 uint16_t reqmax = fr.unsigned_16();
683 militarysite.m_soldier_upgrade_requirements = RequireAttribute(atrTotal, reqmin, reqmax);
684- militarysite.m_soldier_preference = static_cast<MilitarySite::SoldierPreference>(fr.unsigned_8());
685+ militarysite.m_soldier_preference = static_cast<Soldier::SoldierPreference>(fr.unsigned_8());
686 militarysite.m_next_swap_soldiers_time = fr.signed_32();
687 militarysite.m_soldier_upgrade_try = 0 != fr.unsigned_8() ? true : false;
688 militarysite.m_doing_upgrade_request = 0 != fr.unsigned_8() ? true : false;
689
690=== modified file 'src/wui/soldierlist.cc'
691--- src/wui/soldierlist.cc 2016-01-17 08:29:59 +0000
692+++ src/wui/soldierlist.cc 2016-01-24 08:32:15 +0000
693@@ -427,12 +427,12 @@
694 }
695
696 m_soldier_preference.set_state(0);
697- if (ms->get_soldier_preference() == Widelands::MilitarySite::kPrefersHeroes) {
698+ if (ms->get_soldier_preference() == Widelands::Soldier::kPrefersHeroes) {
699 m_soldier_preference.set_state(1);
700 }
701 if (can_act) {
702- m_soldier_preference.changedto.connect
703- (boost::bind(&SoldierList::set_soldier_preference, this, _1));
704+ m_soldier_preference.changedto.connect(
705+ boost::bind(&SoldierList::set_soldier_preference, this, _1));
706 } else {
707 m_soldier_preference.set_enabled(false);
708 }
709@@ -458,13 +458,13 @@
710 }
711 if (upcast(Widelands::MilitarySite, ms, &m_building)) {
712 switch (ms->get_soldier_preference()) {
713- case Widelands::MilitarySite::kPrefersRookies:
714+ case Widelands::Soldier::kPrefersRookies:
715 m_soldier_preference.set_state(0);
716 break;
717- case Widelands::MilitarySite::kPrefersHeroes:
718+ case Widelands::Soldier::kPrefersHeroes:
719 m_soldier_preference.set_state(1);
720 break;
721- case Widelands::MilitarySite::kNoPreference:
722+ case Widelands::Soldier::kNoPreference:
723 m_soldier_preference.set_state(-1);
724 break;
725 }
726@@ -506,8 +506,8 @@
727 #endif
728 m_igb.game().send_player_militarysite_set_soldier_preference
729 (m_building, changed_to == 0 ?
730- Widelands::MilitarySite::kPrefersRookies:
731- Widelands::MilitarySite::kPrefersHeroes);
732+ Widelands::Soldier::kPrefersRookies:
733+ Widelands::Soldier::kPrefersHeroes);
734 }
735
736 UI::Panel * create_soldier_list

Subscribers

People subscribed via source and target branches

to status/vote changes: