Merge lp:~widelands-dev/widelands/find_attack_soldiers into lp:widelands
- find_attack_soldiers
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
GunChleoc | Needs Fixing | ||
SirVer | Approve | ||
TiborB | Approve | ||
Review via email: mp+245276@code.launchpad.net |
Commit message
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.
TiborB (tiborb95) wrote : | # |
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.
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.
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/
- drop least suited soldier for upgrading (default: weakest)
- kicking out any soldier (by the player or for replacement/
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<
- std::deque
However, this would requiere a larger modification of the source code and needs more discussing.
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.
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.
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/
--- 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_
+ return a.priority > b.priority;
+}
+
void sort_soldiers(
- uint32_t max = std::numeric_
- std::vector<
- 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_
- if (level > maxlevel) {
- maxlevel = level;
- maxpos = pos;
- soldier.soldier = soldier_
- soldier.priority = level;
- }
- pos++;
- }
- temp.push_
- soldiers-
- left--;
+ uint32_t max = std::numeric_
+ if (max >= soldiers->size()) {
+ return;
}
- soldiers-
+
+ std::stable_
+ // 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_
+ // soldiers->end(), compare_soldiers);
+ soldiers-
}
}
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/
> --- 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_
> + return a.priority > b.priority;
> +}
> +
> void sort_soldiers(
> - uint32_t max = std::numeric_
> - std::vector<
> - 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_
> - if (level > maxlevel) {
> - maxlevel = level;
> - maxpos = pos;
> - soldier.soldier = soldier_
> - soldier.priority = level;
> - }
> - pos++;
> - }
> - temp.push_
> - soldiers-
> - left--;
> + uint32_t max = std::numeric_
> + if (max >= soldiers->size()) {
> + return;
> }
> - soldiers-
> +
> + std::stable_
> + // 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_
> + // soldiers->end(), compare_soldiers);
> + soldiers-
> }
>
> }
>
>
> --
> https:/
> You proposed lp:~widelands-dev/widelands/find_attack_soldiers for merging.
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 ?
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:/
> Your team Widelands Developers is subscribed to branch
> lp:~widelands-dev/widelands/find_attack_soldiers.
>
> _______
> Mailing list: https:/
> Post to : <email address hidden>
> Unsubscribe : https:/
> More help : https:/
>
SirVer (sirver) wrote : | # |
martin, please ping this merge request when you feel it is ready. Launchpad is not sending notifications for new commits.
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.
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?
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.
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".
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 :(
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.
Martin Schmidt (martinschmidt) wrote : | # |
I will create a bug report as soon as this branch is merged.
TiborB (tiborb95) wrote : | # |
I looked at the code and run some AI-only game - no problems. I think it can go.
TiborB (tiborb95) wrote : | # |
Is this waiting only for a volunteer who would merge it to trunk? I can do it....
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.
SirVer (sirver) wrote : | # |
sorry - did not see that.
GunChleoc (gunchleoc) wrote : | # |
I just found that this breaks the regression tests. The problem is with test/maps/
Martin Schmidt (martinschmidt) wrote : | # |
ok, thanks, I will look into this
bunnybot (widelandsofficial) wrote : | # |
Hi, I am bunnybot (https:/
I am keeping the source branch lp:~widelands-dev/widelands/find_attack_soldiers mirrored to
https:/
The latest continuous integration build can always be found here:
https:/
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.
bunnybot (widelandsofficial) wrote : | # |
Travis build 126 has changed state to: failed. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Travis build 126 has changed state to: failed. Details: https:/
bunnybot (widelandsofficial) wrote : | # |
Travis build 126 has changed state to: failed. Details: https:/
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.
bunnybot (widelandsofficial) wrote : | # |
Bunnybot encountered an error while working on this merge proposal:
<urlopen error [Errno -2] Name or service not known>
bunnybot (widelandsofficial) wrote : | # |
Bunnybot encountered an error while working on this merge proposal:
<urlopen error [Errno -2] Name or service not known>
bunnybot (widelandsofficial) wrote : | # |
Bunnybot encountered an error while working on this merge proposal:
<urlopen error [Errno -2] Name or service not known>
bunnybot (widelandsofficial) wrote : | # |
Bunnybot encountered an error while working on this merge proposal:
<urlopen error [Errno -2] Name or service not known>
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.
SirVer (sirver) wrote : | # |
Abandoning this then.
Preview Diff
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 |
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...