float autocvar_g_balance_ammodistribution_inner_exponent = -1;
float autocvar_g_balance_ammodistribution_outer_exponent = 2;
float autocvar_g_balance_ammodistribution_outer_nonmatch_exponent = -2;
-float autocvar_g_balance_ammodistribution_p3distance = 64 * 3 + 1;
-float autocvar_g_balance_ammodistribution_p2distance = 1024 + 1;
+float autocvar_g_balance_ammodistribution_shortdistance = 64 * 3 + 1;
+float autocvar_g_balance_ammodistribution_mediumdistance = 1024 + 1;
// NOTE: these also exist per weapon
float autocvar_g_balance_ammodistribution_modifier_shells = 1;
float autocvar_g_balance_ammodistribution_modifier_bullets = 1;
self.item_ammo_origin = e.origin;
}
+const float ITEM_AMMO_PRIORITY_UNDEFINED = -1;
+const float ITEM_AMMO_PRIORITY_UNIQUE = 1;
+const float ITEM_AMMO_PRIORITY_FORBIDDEN = 0;
+const float ITEM_AMMO_PRIORITY_NOTTHERE = 2;
+const float ITEM_AMMO_PRIORITY_FAR = 4;
+const float ITEM_AMMO_PRIORITY_MEDIUM = 6;
+const float ITEM_AMMO_PRIORITY_SHORT = 8;
+
float item_ammo_picked;
void item_ammo_pick()
{
+ entity w, a;
+ float i, j;
+
+ // Run only once.
if (item_ammo_picked)
return;
item_ammo_picked = 1;
+ // Find the items we care about.
entity ammolist = findchain(classname, "item_ammo");
entity weaponlist = findchainflags(flags, FL_WEAPON);
- entity w, a;
- float i, j;
-
- float n_w = 0;
- for (w = weaponlist; w; w = w.chain)
- ++n_w;
- float n_a = 0;
+ // Count the ammo items. We'll need this in many places.
+ float n_ammo = 0;
for (a = ammolist; a; a = a.chain)
- ++n_a;
-
- float n_teams = 1;
- if (have_team_spawns > 0)
- {
- CheckAllowedTeams(world);
- n_teams = (c1 >= 0) + (c2 >= 0) + (c3 >= 0) + (c4 >= 0);
- }
+ ++n_ammo;
// Find out how many we want of each item.
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
if (start_weapons & get_weaponinfo(j).weapons)
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
if (get_weaponinfo(j).items & item_ammo_type(i))
- item_ammo_mincount[i] = n_teams;
+ {
+ item_ammo_mincount[i] = 1;
+ // Just 1, even in teamplay! On
+ // properly designed maps, these
+ // low-priority items will end up in an
+ // neutral area.
+ }
+ float n_weapons = 0;
for (w = weaponlist; w; w = w.chain)
{
- float c = 0;
+ float n_ammotypes = 0;
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
if (get_weaponinfo(w.weapon).items & item_ammo_type(i))
- ++c;
- if (!c)
- {
- // Ammoless weapon is not a weapon.
- --n_w;
- // No need to remove it from the list though - no other
- // loop will do anything with it.
+ ++n_ammotypes;
+ if (!n_ammotypes)
continue;
+
+ // Teamed items count as 1 item in total.
+ float n_itemteams = 1;
+ if (w.team)
+ {
+ entity w2;
+ for(w2 = world; (w2 = findfloat(w2, team, w.team)); )
+ if (w2 != w)
+ if(w2.flags & FL_WEAPON)
+ ++n_itemteams;
}
- c = 1.0 / c;
+
+ n_weapons += 1.0 / n_itemteams;
+
+ // A weapon also counts as 1 in total, no matter how many ammo
+ // types it uses.
+ float weight = 1.0 / (n_ammotypes * n_itemteams);
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
if (get_weaponinfo(w.weapon).items & item_ammo_type(i))
{
- item_ammo_count[i] += item_ammo_modifier(i) * item_ammo_weaponmodifier(w.weapon) * c;
- item_ammo_mincount[i] = 0; // n_teams; // Not needed, weapons serve as ammo pickups too.
+ item_ammo_count[i] += item_ammo_modifier(i) * item_ammo_weaponmodifier(w.weapon) * weight;
+ // Weapon exists on the map! No "bonus" for
+ // start weapons needed any more, as the weapon
+ // pickup serves as ammo too.
+ item_ammo_mincount[i] = 0;
}
}
float n_count = 0;
n_count += item_ammo_count[i];
n_mincount += item_ammo_mincount[i];
}
+ // Scale the ratio by the numbers of available guns.
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
{
float c_i = item_ammo_count[i];
float m_i = item_ammo_mincount[i];
- item_ammo_count[i] = m_i + max(0, ceil(c_i * (n_a - n_mincount) / n_count * autocvar_g_balance_ammodistribution_nudgefactor_max));
+ item_ammo_count[i] = m_i + max(0, ceil(c_i * (n_ammo - n_mincount) / n_count * autocvar_g_balance_ammodistribution_nudgefactor_max));
// Obviously >=
- item_ammo_mincount[i] = m_i + max(0, floor(c_i * (n_a - n_mincount) / n_count * autocvar_g_balance_ammodistribution_nudgefactor_min));
+ item_ammo_mincount[i] = m_i + max(0, floor(c_i * (n_ammo - n_mincount) / n_count * autocvar_g_balance_ammodistribution_nudgefactor_min));
dprint(sprintf("Item %s distribution: at least %d, at most %d, distribution input %d %f\n", Item_CounterFieldName(item_ammo_type(i)), item_ammo_mincount[i], item_ammo_count[i], m_i, c_i));
}
+ // Recalculate minimum counts, as we just changed item_ammo_mincount[]
+ n_mincount = 0;
+ for (i = 0; i < ITEM_AMMO_COUNT; ++i)
+ n_mincount += item_ammo_mincount[i];
+
+ // Check for sane distribution.
+ if (n_ammo < n_weapons * 0.5)
+ print("^3WARNING: not enough ammo items. Expect to run out of ammo.\n");
+ if (n_ammo > n_weapons * 2.0)
+ print("^3WARNING: too many ammo items. Expect to swim in ammo.\n");
// Find the weights and priorities.
for (a = ammolist; a; a = a.chain)
{
float totalsum = 0;
- float maxprio = -1;
+ float maxprio = ITEM_AMMO_PRIORITY_UNDEFINED;
float maxprio_count = 0;
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
{
mindist = dist;
sum += pow(dist, autocvar_g_balance_ammodistribution_inner_exponent);
}
- if (sum == 0)
- a.item_ammo_weight[i] = 0;
+ if (mindist < 0)
+ a.item_ammo_weight[i] = weight; // To be fixed later.
else
a.item_ammo_weight[i] = pow(sum, autocvar_g_balance_ammodistribution_outer_exponent) * weight;
- if (mindist < 0)
- a.item_ammo_priority[i] = 0;
- else if (forbid)
- a.item_ammo_priority[i] = 1;
- else if (mindist <= autocvar_g_balance_ammodistribution_p3distance)
- a.item_ammo_priority[i] = 4;
- else if (mindist <= autocvar_g_balance_ammodistribution_p2distance)
- a.item_ammo_priority[i] = 3;
+
+ // Mapper said to not place this item here? FINE.
+ if (forbid)
+ a.item_ammo_priority[i] = ITEM_AMMO_PRIORITY_FORBIDDEN;
+ // Not there? Quite low prio, then.
+ else if (mindist < 0)
+ a.item_ammo_priority[i] = ITEM_AMMO_PRIORITY_NOTTHERE;
+ // Distance based decisions.
+ else if (mindist <= autocvar_g_balance_ammodistribution_shortdistance)
+ a.item_ammo_priority[i] = ITEM_AMMO_PRIORITY_SHORT;
+ else if (mindist <= autocvar_g_balance_ammodistribution_mediumdistance)
+ a.item_ammo_priority[i] = ITEM_AMMO_PRIORITY_MEDIUM;
else
- a.item_ammo_priority[i] = 2;
+ a.item_ammo_priority[i] = ITEM_AMMO_PRIORITY_FAR;
if (a.item_ammo_priority[i] > maxprio)
{
maxprio = a.item_ammo_priority[i];
}
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
{
- float weight = a.(Item_CounterField(item_ammo_type(i)));
- if (a.item_ammo_weight[i] == 0)
- a.item_ammo_weight[i] = pow(totalsum, autocvar_g_balance_ammodistribution_outer_nonmatch_exponent) * weight;
+ // If item is not there at all, we could not assign a
+ // weight. Assign a weight based on the inverse of the
+ // other item weights (i.e. put it where it's least in
+ // the way for other items).
+ if (a.item_ammo_priority[i] == ITEM_AMMO_PRIORITY_NOTTHERE)
+ a.item_ammo_weight[i] *= pow(totalsum, autocvar_g_balance_ammodistribution_outer_nonmatch_exponent);
+ // Boost if the highest priority is unique (respect
+ // mapper's intentions).
if (a.item_ammo_priority[i] == maxprio && maxprio_count == 1)
- a.item_ammo_priority[i] += 0.5;
+ a.item_ammo_priority[i] |= ITEM_AMMO_PRIORITY_UNIQUE;
}
}
for (a = ammolist; a; a = a.chain)
a.item_ammo_chosen_type = -1;
- // Distribute them properly.
- n_mincount = 0;
- for (i = 0; i < ITEM_AMMO_COUNT; ++i)
- n_mincount += item_ammo_mincount[i];
- while (n_a > 0)
+ // Distribute them properly. Remember the highest unfulfilled priority
+ // for warnings.
+ float max_unfulfilled_priority = ITEM_AMMO_PRIORITY_UNDEFINED;
+ while (n_ammo > 0)
{
// Randomly pick one of the remaining item spawn points, and a
// corresponding item type. Honor priorities and weights.
for (a = ammolist; a; a = a.chain)
if (a.item_ammo_chosen_type == -1)
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
- if (((n_a <= n_mincount) ? item_ammo_mincount[i] : item_ammo_count[i]) > 0)
+ if (((n_ammo <= n_mincount) ? item_ammo_mincount[i] : item_ammo_count[i]) > 0)
RandomSelection_Add(a, i, string_null, a.item_ammo_weight[i], a.item_ammo_priority[i]);
a = RandomSelection_chosen_ent;
i = RandomSelection_chosen_float;
+ // Selection failed?
if (!a)
- {
- if (n_mincount >= 0)
- {
- backtrace("Nothing to select. Retrying without mincount... (BTW: FIX YOUR MAP AND/OR THIS CODE)");
- n_mincount = 0;
- continue;
- }
- error("Nothing to select.");
- }
+ error("Nothing to select. Probably a bug in this code.");
+
+ dprint(sprintf("%v (%v): %s -> %s\n", a.origin, a.item_ammo_origin, a.save_classname, Item_CounterFieldName(item_ammo_type(i))));
// Assign the selected item type to the selected item.
a.item_ammo_chosen_type = i;
- dprint(sprintf("%v (%v): %s -> %s\n", a.origin, a.item_ammo_origin, a.save_classname, Item_CounterFieldName(item_ammo_type(i))));
+ // Test for unfulfilled priorities.
+ for (j = 0; j < ITEM_AMMO_COUNT; ++j)
+ if (j != i)
+ if (a.item_ammo_priority[j] >= a.item_ammo_priority[i])
+ if (a.item_ammo_priority[j] > max_unfulfilled_priority)
+ max_unfulfilled_priority = a.item_ammo_priority[j];
// Decrease the count of remaining items.
if (item_ammo_mincount[i] > 0)
--n_mincount;
--item_ammo_mincount[i];
--item_ammo_count[i];
- --n_a;
+ --n_ammo;
}
- for (i = 0; i < ITEM_AMMO_COUNT; ++i)
+ // Report the highest unfulfilled priority.
+ switch (max_unfulfilled_priority)
{
- if (item_ammo_count[i] - item_ammo_mincount[i] >= 2)
- {
- if (item_ammo_mincount[i] == 0)
- print(sprintf("WARNING: map might not have enough item_ammo near %s weapons\n", Item_CounterFieldName(item_ammo_type(i))));
- if (item_ammo_count[i] == 0)
- print(sprintf("WARNING: map might have too many item_ammo near %s weapons\n", Item_CounterFieldName(item_ammo_type(i))));
- }
+ case ITEM_AMMO_PRIORITY_SHORT | ITEM_AMMO_PRIORITY_UNIQUE:
+ print("^3WARNING: short-range distribution could not be fulfilled with the available item counts. Random selection has taken place.\n");
+ break;
+ case ITEM_AMMO_PRIORITY_SHORT:
+ print("^3WARNING: short-range distribution is ambiguous. Random selection has taken place.\n");
+ break;
+ case ITEM_AMMO_PRIORITY_MEDIUM | ITEM_AMMO_PRIORITY_UNIQUE:
+ print("^3WARNING: medium-range distribution could not be fulfilled with the available item counts. Random selection has taken place.\n");
+ break;
+ case ITEM_AMMO_PRIORITY_MEDIUM:
+ print("^3WARNING: medium-range distribution is ambiguous. Random selection has taken place.\n");
+ break;
}
+ // Recount the item counts for debugging.
for (i = 0; i < ITEM_AMMO_COUNT; ++i)
item_ammo_count[i] = 0;
for (a = ammolist; a; a = a.chain)