Add a new "rechargeable" flag to items
A way to set this flag is not yet exposed in the scenario editor. The flag is intended only for non-stackable items, but this currently isn't enforced. Items now have a maximum number of charges, which is equal to their default number set in the item record. Enchanted items with charges are now rechargeable.
This commit is contained in:
@@ -106,6 +106,7 @@
|
||||
<xs:element name="cursed" type="bool" minOccurs="0"/>
|
||||
<xs:element name="concealed" type="bool" minOccurs="0"/>
|
||||
<xs:element name="enchanted" type="bool" minOccurs="0"/>
|
||||
<xs:element name="rechargeable" type="bool" minOccurs="0"/>
|
||||
<xs:element name="unsellable" type="bool" minOccurs="0"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
|
@@ -391,8 +391,8 @@ arrows, bolts, thrown missiles, or missiles with no ammo).
|
||||
`help-one`, `harm-all`, `help-all`.
|
||||
* `<properties>` - Contains several boolean subtags specifying properties of this item.
|
||||
Recognized subtags are `<identified>` (indicating it is _always_ identified), `<magic>`,
|
||||
`<cursed>`, `<concealed>`, `<enchanted>`, `<unsellable>`. Note that the editor UI gives no
|
||||
access to the `<enchanted>` flag.
|
||||
`<cursed>`, `<concealed>`, `<enchanted>`, `<unsellable>`, `<rechargeable>`.
|
||||
Note that the editor UI gives no access to the `<enchanted>` flag.
|
||||
* `<description>` - A description of the item. The scenario editor wraps the contents of
|
||||
this element in a `CDATA` declaration.
|
||||
|
||||
|
@@ -1112,6 +1112,7 @@ void readItemsFromXml(ticpp::Document&& data, cScenario& scenario) {
|
||||
item->GetText(&the_item.protection);
|
||||
} else if(type == "charges") {
|
||||
item->GetText(&the_item.charges);
|
||||
the_item.max_charges = the_item.charges;
|
||||
} else if(type == "weapon-type") {
|
||||
item->GetText(&the_item.weap_type);
|
||||
} else if(type == "missile-type") {
|
||||
@@ -1169,6 +1170,8 @@ void readItemsFromXml(ticpp::Document&& data, cScenario& scenario) {
|
||||
the_item.concealed = state();
|
||||
} else if(type == "enchanted") {
|
||||
the_item.enchanted = state();
|
||||
} else if(type == "rechargeable") {
|
||||
the_item.rechargeable = state();
|
||||
} else if(type == "unsellable") {
|
||||
the_item.unsellable = state();
|
||||
} else throw xBadNode(type, prop->Row(), prop->Column(), fname);
|
||||
|
@@ -971,7 +971,7 @@ void handle_item_shop_action(short item_hit) {
|
||||
else {
|
||||
play_sound(68);
|
||||
ASB("Your item is recharged.");
|
||||
target.charges += 5;
|
||||
target.charges = target.max_charges;
|
||||
}
|
||||
break;
|
||||
case MODE_SELL_WEAP: case MODE_SELL_ARMOR: case MODE_SELL_ANY:
|
||||
|
@@ -603,6 +603,10 @@ void use_item(short pc,short item) {
|
||||
add_string_to_buf("Use: Can't use this item.");
|
||||
take_charge = false;
|
||||
}
|
||||
if(item_rec.rechargeable && item_rec.charges == 0) {
|
||||
add_string_to_buf("Use: No charges left.");
|
||||
take_charge = false;
|
||||
}
|
||||
if(univ.party[pc].traits[eTrait::MAGICALLY_INEPT] && !inept_ok){
|
||||
add_string_to_buf("Use: Can't - magically inept.");
|
||||
take_charge = false;
|
||||
|
@@ -335,7 +335,7 @@ void put_item_screen(eItemWinMode screen_num) {
|
||||
if((stat_screen_mode == MODE_SHOP) &&
|
||||
((is_town()) || (is_out()) || ((is_combat()) && (pc == univ.cur_pc)))) { // place give and drop and use
|
||||
place_item_graphic(i,item.graphic_num);
|
||||
if(item.can_use()) // place use if can
|
||||
if(item.can_use() && (item.rechargeable ? item.charges > 0 : true)) // place use if can
|
||||
place_item_button(ITEMBTN_NORM,i);
|
||||
else place_item_button(ITEMBTN_ALL,i);
|
||||
}
|
||||
@@ -346,7 +346,7 @@ void put_item_screen(eItemWinMode screen_num) {
|
||||
((is_town()) || (is_out()) || ((is_combat()) && (pc == univ.cur_pc)))) { // place give and drop and use
|
||||
place_item_button(1,i,ITEMBTN_GIVE);
|
||||
place_item_button(2,i,ITEMBTN_DROP);
|
||||
if(item.can_use()) // place use if can
|
||||
if(item.can_use() && (item.rechargeable ? item.charges > 0 : true)) // place use if can
|
||||
place_item_button(0,i,ITEMBTN_USE);
|
||||
}
|
||||
}
|
||||
@@ -393,7 +393,7 @@ void place_buy_button(short position,short pc_num,short item_num) {
|
||||
}
|
||||
break;
|
||||
case MODE_RECHARGE:
|
||||
if(item.charges == 0 && item.can_use()) {
|
||||
if(item.rechargeable && item.charges == 0 && item.can_use()) {
|
||||
item_area_button_active[position][ITEMBTN_SPEC] = true;
|
||||
source_rect = button_sources[3];
|
||||
val_to_place = shop_identify_cost;
|
||||
|
@@ -195,7 +195,7 @@ cItem::cItem(){
|
||||
awkward = 0;
|
||||
bonus = 0;
|
||||
protection = 0;
|
||||
charges = 0;
|
||||
charges = max_charges = 0;
|
||||
weap_type = eSkill::INVALID;
|
||||
magic_use_type = eItemUse::HELP_ONE;
|
||||
graphic_num = 0;
|
||||
@@ -212,7 +212,7 @@ cItem::cItem(){
|
||||
item_loc.y = 0;
|
||||
treas_class = 0;
|
||||
ident = property = magic = contained = held = false;
|
||||
cursed = concealed = enchanted = unsellable = false;
|
||||
cursed = concealed = enchanted = rechargeable = unsellable = false;
|
||||
}
|
||||
|
||||
cItem::cItem(eItemPreset preset) : cItem() {
|
||||
@@ -336,6 +336,7 @@ cItem::cItem(eItemPreset preset) : cItem() {
|
||||
graphic_num = 105; // The blank graphic
|
||||
break;
|
||||
}
|
||||
max_charges = charges;
|
||||
}
|
||||
|
||||
cItem::cItem(eAlchemy recipe) : cItem(ITEM_POTION) {
|
||||
@@ -366,7 +367,8 @@ void cItem::enchant_weapon(eEnchant enchant_type) {
|
||||
abil_data = info.abil_data;
|
||||
}
|
||||
if(info.charges > 0) {
|
||||
charges = info.charges;
|
||||
charges = max_charges = info.charges;
|
||||
rechargeable = true;
|
||||
}
|
||||
if(value > 15000)
|
||||
value = 15000;
|
||||
@@ -381,7 +383,7 @@ void cItem::import_legacy(legacy::item_record_type& old){
|
||||
awkward = old.awkward;
|
||||
bonus = old.bonus;
|
||||
protection = old.protection;
|
||||
charges = old.charges;
|
||||
charges = max_charges = old.charges;
|
||||
if(old.type >= 1 && old.type <= 3)
|
||||
weap_type = eSkill(old.type + 2);
|
||||
else weap_type = eSkill::INVALID;
|
||||
@@ -923,7 +925,7 @@ void cItem::import_legacy(legacy::item_record_type& old){
|
||||
contained = old.item_properties & 8;
|
||||
cursed = old.item_properties & 16;
|
||||
concealed = old.item_properties & 32;
|
||||
enchanted = held = false;
|
||||
enchanted = rechargeable = held = false;
|
||||
unsellable = old.item_properties & 16;
|
||||
// Set missile, if needed
|
||||
if(variety == eItemType::ARROW || variety == eItemType::BOLTS) {
|
||||
@@ -1260,7 +1262,7 @@ void cItem::writeTo(cTagFile_Page& page) const {
|
||||
page["AWKWARD"] << awkward;
|
||||
page["BONUS"] << bonus;
|
||||
page["PROT"] << protection;
|
||||
page["CHARGES"] << charges;
|
||||
page["CHARGES"] << charges << max_charges;
|
||||
page["WEAPON"] << weap_type;
|
||||
page["USE"] << magic_use_type;
|
||||
page["ICON"] << graphic_num;
|
||||
@@ -1285,6 +1287,7 @@ void cItem::writeTo(cTagFile_Page& page) const {
|
||||
if(cursed) page.add("CURSED");
|
||||
if(concealed) page.add("CONCEALED");
|
||||
if(enchanted) page.add("ENCHANTED");
|
||||
if(rechargeable) page.add("RECHARGEABLE");
|
||||
if(unsellable) page.add("UNSELLABLE");
|
||||
}
|
||||
|
||||
@@ -1294,7 +1297,7 @@ void cItem::readFrom(const cTagFile_Page& page){
|
||||
page["AWKWARD"] >> awkward;
|
||||
page["BONUS"] >> bonus;
|
||||
page["PROT"] >> protection;
|
||||
page["CHARGES"] >> charges;
|
||||
page["CHARGES"] >> charges >> max_charges;
|
||||
page["WEAPON"] >> weap_type;
|
||||
page["USE"] >> magic_use_type;
|
||||
page["ICON"] >> graphic_num;
|
||||
@@ -1319,6 +1322,7 @@ void cItem::readFrom(const cTagFile_Page& page){
|
||||
cursed = page.contains("CURSED");
|
||||
concealed = page.contains("CONCEALED");
|
||||
enchanted = page.contains("ENCHANTED");
|
||||
rechargeable = page.contains("RECHARGEABLE");
|
||||
unsellable = page.contains("UNSELLABLE");
|
||||
}
|
||||
|
||||
|
@@ -46,7 +46,7 @@ public:
|
||||
int awkward;
|
||||
int bonus;
|
||||
int protection;
|
||||
int charges;
|
||||
int charges, max_charges;
|
||||
eSkill weap_type;
|
||||
eItemUse magic_use_type;
|
||||
unsigned short graphic_num;
|
||||
@@ -63,7 +63,7 @@ public:
|
||||
std::string full_name;
|
||||
std::string name;
|
||||
unsigned int treas_class;
|
||||
bool ident, property, magic, contained, held, cursed, concealed, enchanted, unsellable;
|
||||
bool ident, property, magic, contained, held, cursed, concealed, enchanted, unsellable, rechargeable;
|
||||
std::string desc;
|
||||
unsigned char rec_treas_class() const;
|
||||
short item_weight() const;
|
||||
|
@@ -464,6 +464,7 @@ void writeItemsToXml(ticpp::Printer&& data, cScenario& scenario) {
|
||||
if(item.cursed) data.PushElement("cursed", "true");
|
||||
if(item.concealed) data.PushElement("concealed", "true");
|
||||
if(item.enchanted) data.PushElement("enchanted", "true");
|
||||
if(item.rechargeable) data.PushElement("rechargeable", "true");
|
||||
if(item.unsellable) data.PushElement("unsellable", "true");
|
||||
data.CloseElement("properties");
|
||||
|
||||
|
@@ -629,7 +629,7 @@ bool cParty::take_abil(eItemAbil abil, short dat) {
|
||||
for(int i = 0; i < 6; i++)
|
||||
if(adven[i]->main_status == eMainStatus::ALIVE)
|
||||
if(cInvenSlot item = adven[i]->has_abil(abil,dat)) {
|
||||
if(item->charges > 1)
|
||||
if(item->charges > 1 || item->rechargeable)
|
||||
item->charges--;
|
||||
else adven[i]->take_item(item.slot);
|
||||
return true;
|
||||
@@ -637,12 +637,12 @@ bool cParty::take_abil(eItemAbil abil, short dat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cParty::has_class(unsigned int item_class) {
|
||||
bool cParty::has_class(unsigned int item_class, bool require_charges) {
|
||||
if(item_class == 0)
|
||||
return false;
|
||||
for(auto& pc : *this)
|
||||
if(pc.main_status == eMainStatus::ALIVE)
|
||||
if(cInvenSlot item = pc.has_class(item_class)) {
|
||||
if(cInvenSlot item = pc.has_class(item_class, require_charges)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -653,8 +653,8 @@ bool cParty::take_class(unsigned int item_class) const {
|
||||
return false;
|
||||
for(auto& pc : *this)
|
||||
if(pc.main_status == eMainStatus::ALIVE)
|
||||
if(cInvenSlot item = pc.has_class(item_class)) {
|
||||
if(item->charges > 1)
|
||||
if(cInvenSlot item = pc.has_class(item_class, true)) {
|
||||
if(item->charges > 1 || item->rechargeable)
|
||||
item->charges--;
|
||||
else pc.take_item(item.slot);
|
||||
return true;
|
||||
|
@@ -203,7 +203,7 @@ public:
|
||||
bool forced_give(cItem item,eItemAbil abil,short dat = -1);
|
||||
bool has_abil(eItemAbil abil, short dat = -1) const;
|
||||
bool take_abil(eItemAbil abil, short dat = -1);
|
||||
bool has_class(unsigned int item_class);
|
||||
bool has_class(unsigned int item_class, bool require_charges = false);
|
||||
bool take_class(unsigned int item_class) const;
|
||||
|
||||
bool start_split(short x, short y, snd_num_t noise, short who);
|
||||
|
@@ -768,6 +768,7 @@ cInvenSlot cPlayer::has_abil(eItemAbil abil,short dat) {
|
||||
return find_item_matching([this,abil,dat](int, const cItem& item) {
|
||||
if(item.variety == eItemType::NO_ITEM) return false;
|
||||
if(item.ability != abil) return false;
|
||||
if(item.charges == 0) return false;
|
||||
if(dat >= 0 && dat != item.abil_data.value) return false;
|
||||
return true;
|
||||
});
|
||||
@@ -807,14 +808,14 @@ const cInvenSlot cPlayer::has_class_equip(unsigned int item_class) const {
|
||||
return const_cast<cPlayer*>(this)->has_class_equip(item_class);
|
||||
}
|
||||
|
||||
cInvenSlot cPlayer::has_class(unsigned int item_class) {
|
||||
return find_item_matching([item_class](int, const cItem& item) {
|
||||
return item.special_class == item_class;
|
||||
cInvenSlot cPlayer::has_class(unsigned int item_class, bool require_charges) {
|
||||
return find_item_matching([item_class, require_charges](int, const cItem& item) {
|
||||
return item.special_class == item_class && (!require_charges || item.charges > 0);
|
||||
});
|
||||
}
|
||||
|
||||
const cInvenSlot cPlayer::has_class(unsigned int item_class) const {
|
||||
return const_cast<cPlayer*>(this)->has_class(item_class);
|
||||
const cInvenSlot cPlayer::has_class(unsigned int item_class, bool require_charges) const {
|
||||
return const_cast<cPlayer*>(this)->has_class(item_class, require_charges);
|
||||
}
|
||||
|
||||
cInvenSlot::operator bool() const {
|
||||
@@ -894,7 +895,7 @@ void cPlayer::take_item(int which_item) {
|
||||
void cPlayer::remove_charge(int which_item) {
|
||||
if(items[which_item].charges > 0) {
|
||||
items[which_item].charges--;
|
||||
if(items[which_item].charges == 0) {
|
||||
if(items[which_item].charges == 0 && !items[which_item].rechargeable) {
|
||||
take_item(which_item);
|
||||
}
|
||||
}
|
||||
|
@@ -168,8 +168,8 @@ public:
|
||||
cInvenSlot has_type(eItemType type);
|
||||
const cInvenSlot has_class_equip(unsigned int item_class) const;
|
||||
cInvenSlot has_class_equip(unsigned int item_class);
|
||||
const cInvenSlot has_class(unsigned int item_class) const;
|
||||
cInvenSlot has_class(unsigned int item_class);
|
||||
const cInvenSlot has_class(unsigned int item_class, bool require_charges = false) const;
|
||||
cInvenSlot has_class(unsigned int item_class, bool require_charges = false);
|
||||
|
||||
short skill(eSkill skill) const;
|
||||
short stat_adj(eSkill skill) const;
|
||||
|
@@ -28,8 +28,9 @@
|
||||
<cursed>true</cursed>
|
||||
<concealed>true</concealed>
|
||||
<enchanted>true</enchanted>
|
||||
<rechargeable>true</rechargeable>
|
||||
<unsellable>true</unsellable>
|
||||
</properties>
|
||||
<description>This is a silly, silly description.</description>
|
||||
</item>
|
||||
</items>
|
||||
</items>
|
||||
|
@@ -37,6 +37,7 @@ TEST_CASE("Converting items from legacy scenarios") {
|
||||
CHECK(new_item.bonus == 1);
|
||||
CHECK(new_item.protection == 3);
|
||||
CHECK(new_item.charges == 10);
|
||||
CHECK(new_item.max_charges == 10);
|
||||
CHECK(new_item.graphic_num == 62);
|
||||
CHECK(new_item.type_flag == 100);
|
||||
CHECK(new_item.value == 500);
|
||||
|
@@ -116,6 +116,7 @@ TEST_CASE("Loading an item type definition") {
|
||||
CHECK(scen.scen_items[0].bonus == 5);
|
||||
CHECK(scen.scen_items[0].protection == 4);
|
||||
CHECK(scen.scen_items[0].charges == 20);
|
||||
CHECK(scen.scen_items[0].max_charges == 20);
|
||||
CHECK(scen.scen_items[0].weap_type == eSkill::DEFENSE);
|
||||
CHECK(scen.scen_items[0].missile == 3);
|
||||
CHECK(scen.scen_items[0].type_flag == 9);
|
||||
@@ -130,6 +131,7 @@ TEST_CASE("Loading an item type definition") {
|
||||
CHECK(scen.scen_items[0].cursed);
|
||||
CHECK(scen.scen_items[0].concealed);
|
||||
CHECK(scen.scen_items[0].enchanted);
|
||||
CHECK(scen.scen_items[0].rechargeable);
|
||||
CHECK(scen.scen_items[0].unsellable);
|
||||
CHECK(scen.scen_items[0].desc == "This is a silly, silly description.");
|
||||
}
|
||||
|
@@ -78,6 +78,7 @@ TEST_CASE("Saving item types") {
|
||||
scen.scen_items[0].cursed = true;
|
||||
scen.scen_items[0].concealed = true;
|
||||
scen.scen_items[0].enchanted = true;
|
||||
scen.scen_items[0].rechargeable = true;
|
||||
scen.scen_items[0].unsellable = true;
|
||||
scen.scen_items[0].desc = " This is a silly, silly description. ";
|
||||
in_and_out("full", scen);
|
||||
@@ -86,6 +87,7 @@ TEST_CASE("Saving item types") {
|
||||
CHECK(scen.scen_items[0].bonus == 5);
|
||||
CHECK(scen.scen_items[0].protection == 4);
|
||||
CHECK(scen.scen_items[0].charges == 20);
|
||||
CHECK(scen.scen_items[0].max_charges == 20);
|
||||
CHECK(scen.scen_items[0].weap_type == eSkill::DEFENSE);
|
||||
CHECK(scen.scen_items[0].missile == 3);
|
||||
CHECK(scen.scen_items[0].type_flag == 9);
|
||||
@@ -100,6 +102,7 @@ TEST_CASE("Saving item types") {
|
||||
CHECK(scen.scen_items[0].cursed);
|
||||
CHECK(scen.scen_items[0].concealed);
|
||||
CHECK(scen.scen_items[0].enchanted);
|
||||
CHECK(scen.scen_items[0].rechargeable);
|
||||
CHECK(scen.scen_items[0].unsellable);
|
||||
CHECK(scen.scen_items[0].desc == " This is a silly, silly description. ");
|
||||
}
|
||||
|
Reference in New Issue
Block a user