feat(fifa17): apply the rare all-six training card
Passes a null attribute slot for the rare card -- Core reads absence as "every slot", so sending 0 would silently train pace alone -- and declares the per-family ceiling rather than a single constant. Tests pin the null-slot serialisation, both ceilings, and that all 36 single-attribute plus all 6 rare cards resolve.
This commit is contained in:
@@ -91,7 +91,7 @@ use openfut_adapter_fifa17::fut::store_session::{
|
|||||||
validate_capability, SessionStore, StoreMode, SENTINEL_PACK_ID,
|
validate_capability, SessionStore, StoreMode, SENTINEL_PACK_ID,
|
||||||
};
|
};
|
||||||
use openfut_adapter_fifa17::fut::training_cards::{
|
use openfut_adapter_fifa17::fut::training_cards::{
|
||||||
class_accepts_position, training_effect, TRAINING_MAX_AMOUNT,
|
ceiling_for, class_accepts_position, training_effect,
|
||||||
};
|
};
|
||||||
use openfut_identity::ExternalIdentityStore;
|
use openfut_identity::ExternalIdentityStore;
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
@@ -1316,7 +1316,8 @@ pub enum ApplyEffect {
|
|||||||
/// out of `cardsubtypeid` by the adapter — Core is never told a FIFA
|
/// out of `cardsubtypeid` by the adapter — Core is never told a FIFA
|
||||||
/// attribute name.
|
/// attribute name.
|
||||||
ApplyTraining {
|
ApplyTraining {
|
||||||
attribute_index: i64,
|
/// `None` = the rare card that boosts all six attributes.
|
||||||
|
attribute_index: Option<i64>,
|
||||||
amount: i64,
|
amount: i64,
|
||||||
max_amount: i64,
|
max_amount: i64,
|
||||||
},
|
},
|
||||||
@@ -1344,6 +1345,9 @@ impl ApplyEffect {
|
|||||||
amount,
|
amount,
|
||||||
max_amount,
|
max_amount,
|
||||||
} => json!({
|
} => json!({
|
||||||
|
// `attribute_index` is deliberately null for the rare all-six
|
||||||
|
// card: Core reads absence as "every slot", so omitting the key
|
||||||
|
// or sending 0 would silently train pace only.
|
||||||
"kind": "apply_training",
|
"kind": "apply_training",
|
||||||
"attribute_index": attribute_index,
|
"attribute_index": attribute_index,
|
||||||
"amount": amount,
|
"amount": amount,
|
||||||
@@ -5087,10 +5091,14 @@ impl Server {
|
|||||||
);
|
);
|
||||||
return error_response(409, "training_target_class_mismatch");
|
return error_response(409, "training_target_class_mismatch");
|
||||||
}
|
}
|
||||||
|
// The ceiling is per-family: 15 for a single attribute, 10 for the
|
||||||
|
// rare all-six card. Sending the single-attribute ceiling for an
|
||||||
|
// all-six card would let Core accept a +15 all-six boost that EA
|
||||||
|
// never authored.
|
||||||
let effect = ApplyEffect::ApplyTraining {
|
let effect = ApplyEffect::ApplyTraining {
|
||||||
attribute_index: t.attribute_index,
|
attribute_index: t.attribute_index,
|
||||||
amount: t.amount,
|
amount: t.amount,
|
||||||
max_amount: TRAINING_MAX_AMOUNT,
|
max_amount: ceiling_for(&t),
|
||||||
};
|
};
|
||||||
return self.finish_consumable_apply(
|
return self.finish_consumable_apply(
|
||||||
econ,
|
econ,
|
||||||
|
|||||||
@@ -2653,7 +2653,7 @@ fn the_training_effect_matches_cores_closed_vocabulary() {
|
|||||||
use openfut_utas_host::ApplyEffect;
|
use openfut_utas_host::ApplyEffect;
|
||||||
|
|
||||||
let json = ApplyEffect::ApplyTraining {
|
let json = ApplyEffect::ApplyTraining {
|
||||||
attribute_index: 4,
|
attribute_index: Some(4),
|
||||||
amount: 10,
|
amount: 10,
|
||||||
max_amount: 15,
|
max_amount: 15,
|
||||||
}
|
}
|
||||||
@@ -2668,6 +2668,24 @@ fn the_training_effect_matches_cores_closed_vocabulary() {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The rare all-six card MUST serialise a null slot. Sending 0 would train
|
||||||
|
// pace alone, and omitting the key would leave Core guessing.
|
||||||
|
let all_six = ApplyEffect::ApplyTraining {
|
||||||
|
attribute_index: None,
|
||||||
|
amount: 10,
|
||||||
|
max_amount: 10,
|
||||||
|
}
|
||||||
|
.to_json();
|
||||||
|
assert_eq!(
|
||||||
|
all_six,
|
||||||
|
serde_json::json!({
|
||||||
|
"kind": "apply_training",
|
||||||
|
"attribute_index": null,
|
||||||
|
"amount": 10,
|
||||||
|
"max_amount": 10,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
// The contract arm must keep its own shape while sharing the enum.
|
// The contract arm must keep its own shape while sharing the enum.
|
||||||
let contract = ApplyEffect::AddContractMatches {
|
let contract = ApplyEffect::AddContractMatches {
|
||||||
amount: 3,
|
amount: 3,
|
||||||
@@ -2691,18 +2709,33 @@ fn the_training_effect_matches_cores_closed_vocabulary() {
|
|||||||
/// refuses a legitimate card — a silent, family-wide outage.
|
/// refuses a legitimate card — a silent, family-wide outage.
|
||||||
#[test]
|
#[test]
|
||||||
fn no_shipped_training_card_exceeds_the_declared_ceiling() {
|
fn no_shipped_training_card_exceeds_the_declared_ceiling() {
|
||||||
use openfut_adapter_fifa17::fut::training_cards::{training_effect, TRAINING_MAX_AMOUNT};
|
use openfut_adapter_fifa17::fut::training_cards::{
|
||||||
|
ceiling_for, training_effect, TRAINING_ALL_MAX_AMOUNT, TRAINING_MAX_AMOUNT,
|
||||||
|
};
|
||||||
|
|
||||||
let mut resolved = 0;
|
let mut resolved = 0;
|
||||||
for subtype in [51, 52, 53, 54, 55, 56, 61, 62, 63, 64, 65, 66] {
|
for subtype in [51, 52, 53, 54, 55, 56, 61, 62, 63, 64, 65, 66] {
|
||||||
for amount in [5, 10, 15] {
|
for amount in [5, 10, 15] {
|
||||||
let e = training_effect(subtype, Some(amount)).expect("shipped card resolves");
|
let e = training_effect(subtype, Some(amount)).expect("shipped card resolves");
|
||||||
assert!(
|
assert!(
|
||||||
e.amount <= TRAINING_MAX_AMOUNT,
|
e.amount <= ceiling_for(&e),
|
||||||
"subtype {subtype} amount {amount} exceeds the ceiling sent to Core"
|
"subtype {subtype} amount {amount} exceeds the ceiling sent to Core"
|
||||||
);
|
);
|
||||||
|
assert_eq!(ceiling_for(&e), TRAINING_MAX_AMOUNT);
|
||||||
resolved += 1;
|
resolved += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert_eq!(resolved, 36, "all 36 attribute training cards must resolve");
|
assert_eq!(resolved, 36, "all 36 single-attribute cards must resolve");
|
||||||
|
|
||||||
|
// The six rare all-six cards carry their own, lower ceiling.
|
||||||
|
let mut rare = 0;
|
||||||
|
for subtype in [57, 67] {
|
||||||
|
for amount in [3, 6, 10] {
|
||||||
|
let e = training_effect(subtype, Some(amount)).expect("rare card resolves");
|
||||||
|
assert_eq!(e.attribute_index, None);
|
||||||
|
assert_eq!(ceiling_for(&e), TRAINING_ALL_MAX_AMOUNT);
|
||||||
|
rare += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(rare, 6, "all 6 rare all-six cards must resolve");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user