New special nodes:
- Town set attitude for affecting single creature, adapted from Windows code; technically redundant, but maybe handy - If numeric response - Print nums (for debugging) - SDF arithmetic - add, subtract, multiply, divide, exponentiate - Store random to SDF (adapted from Windows code) - Display picture (inspired by Windows code, but the implementation is completely different and totally incompatible)
This commit is contained in:
@@ -1147,6 +1147,29 @@ short custom_choice_dialog(std::array<std::string, 6>& strs,short pic_num,ePicTy
|
||||
return -1;
|
||||
}
|
||||
|
||||
void custom_pic_dialog(std::string title, pic_num_t bigpic) {
|
||||
cDialog pic_dlg("show-map.xml");
|
||||
cControl& okay = pic_dlg["okay"];
|
||||
cControl& text = pic_dlg["title"];
|
||||
okay.attachClickHandler(std::bind(&cDialog::toast, &pic_dlg, false));
|
||||
text.setText(title);
|
||||
cPict& map = dynamic_cast<cPict&>(pic_dlg["map"]);
|
||||
// We don't provide a way to use non-custom full sheets - why would you want to show standard help graphics?
|
||||
map.setPict(bigpic, PIC_CUSTOM_FULL);
|
||||
// Now we need to adjust the size to ensure that everything fits correctly.
|
||||
map.recalcRect();
|
||||
RECT mapBounds = map.getBounds();
|
||||
RECT btnBounds = okay.getBounds();
|
||||
RECT txtBounds = text.getBounds();
|
||||
btnBounds.offset(-btnBounds.left, -btnBounds.top);
|
||||
btnBounds.offset(mapBounds.right - btnBounds.width(), mapBounds.bottom + 10);
|
||||
okay.setBounds(btnBounds);
|
||||
txtBounds.right = mapBounds.right;
|
||||
text.setBounds(txtBounds);
|
||||
pic_dlg.recalcRect();
|
||||
pic_dlg.run();
|
||||
}
|
||||
|
||||
//short fancy_choice_dialog(short which_dlog,short parent)
|
||||
//// ignore parent in Mac version
|
||||
//{
|
||||
@@ -1471,4 +1494,30 @@ std::string get_text_response(std::string prompt, pic_num_t pic) {
|
||||
return result;
|
||||
}
|
||||
|
||||
short get_num_response(short min, short max, std::string prompt) {
|
||||
std::ostringstream sout(prompt);
|
||||
|
||||
make_cursor_sword();
|
||||
|
||||
cDialog numPanel("get-num.xml");
|
||||
numPanel.attachClickHandlers(get_num_of_items_event_filter, {"okay"});
|
||||
|
||||
sout << " (" << min << '-' << max << ')';
|
||||
numPanel["prompt"].setText(sout.str());
|
||||
numPanel["number"].setTextToNum(0);
|
||||
if(min < max)
|
||||
numPanel["number"].attachFocusHandler([min,max](cDialog& me,std::string,bool losing) -> bool {
|
||||
if(!losing) return true;
|
||||
int val = me["number"].getTextAsNum();
|
||||
if(val < min || val > max) {
|
||||
giveError("Number out of range!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
numPanel.run();
|
||||
|
||||
return numPanel.getResult<int>();
|
||||
}
|
||||
|
||||
|
||||
|
@@ -39,7 +39,7 @@ void set_town_attitude(short lo,short hi,short att);
|
||||
bool show_get_items(std::string titleText, std::vector<cItemRec*>& itemRefs, short pc_getting, bool overload = false);
|
||||
bool display_item(location from_loc,short pc_num,short mode, bool check_container);
|
||||
short custom_choice_dialog(std::array<std::string, 6>& strs,short pic_num,ePicType pic_type,std::array<short, 3>& buttons) ;
|
||||
//short fancy_choice_dialog(short which_dlog,short parent);
|
||||
void custom_pic_dialog(std::string title, pic_num_t bigpic);
|
||||
short get_num_of_items(short max_num);
|
||||
void init_mini_map();
|
||||
void put_pc_effects_on_dialog(cDialog& dialog,short item);
|
||||
@@ -56,6 +56,7 @@ short luck_total();
|
||||
cItemRec return_treasure(short loot);
|
||||
void refresh_store_items();
|
||||
std::string get_text_response(std::string prompt = "", pic_num_t pic = 16);
|
||||
short get_num_response(short min, short max, std::string prompt);
|
||||
|
||||
// These are defined in pc.editors.cpp since they are also used by the character editor
|
||||
short char_select_pc(short active_only,short free_inv_only,const char *title);
|
||||
|
@@ -2080,7 +2080,7 @@ void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
|
||||
{
|
||||
bool check_mess = false;
|
||||
std::string str1,str2;
|
||||
short store_val = 0,i;
|
||||
short store_val = 0,i,j;
|
||||
cSpecial spec;
|
||||
short mess_adj[3] = {160,10,0};
|
||||
|
||||
@@ -2220,6 +2220,79 @@ void general_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
|
||||
set_campaign_flag(spec.sd1,spec.sd2,spec.ex1a,spec.ex1b,spec.m1,spec.ex2a);
|
||||
}
|
||||
break;
|
||||
case eSpecType::DISPLAY_PICTURE:
|
||||
// TODO: In addition to the large picture, there's a small icon; should that be customizable?
|
||||
check_mess = false;
|
||||
get_strs(str1, str1, cur_spec_type, spec.m1, -1);
|
||||
custom_pic_dialog(str1, spec.ex1a);
|
||||
break;
|
||||
case eSpecType::SDF_RANDOM:
|
||||
check_mess = true;
|
||||
|
||||
short rand;
|
||||
// Automatically fix the range in case some idiot puts it in backwards, or the same (WHY)
|
||||
if (cur_node.ex1a == cur_node.ex1b) {
|
||||
rand = cur_node.ex1b;
|
||||
} else {
|
||||
rand = get_ran(1,
|
||||
min(cur_node.ex1a,cur_node.ex1b),
|
||||
max(cur_node.ex1a,cur_node.ex1b)
|
||||
);
|
||||
}
|
||||
setsd(cur_node.sd1,cur_node.sd2,rand);
|
||||
//print_nums(rand, cur_node.ex1a, cur_node.ex1b);
|
||||
break;
|
||||
// SDF arithmetic! :D
|
||||
/*
|
||||
SDF1, SDF2 - Output SDF (for division, the quotient)
|
||||
Ex1a, Ex1b - Input SDF (left operand) - if ex1b is -1, takes ex1a as a literal value (which must be positive)
|
||||
Ex2a, Ex2b - Input SDF (right operand) - if ex2b is -1, takes ex2a as a literal value (which must be positive)
|
||||
Ex1c, Ex2c - For division only, output SDF to store the remainder.
|
||||
*/
|
||||
case eSpecType::SDF_ADD: case eSpecType::SDF_DIFF:
|
||||
case eSpecType::SDF_TIMES: case eSpecType::SDF_POWER:
|
||||
case eSpecType::SDF_DIVIDE:
|
||||
i = spec.ex1b == -1 ? spec.ex1a : PSD[spec.ex1a][spec.ex1b];
|
||||
j = spec.ex2b == -1 ? spec.ex2a : PSD[spec.ex2a][spec.ex2b];
|
||||
switch(spec.type) {
|
||||
case eSpecType::SDF_ADD: setsd(spec.sd1, spec.sd2, i + j); break;
|
||||
case eSpecType::SDF_DIFF: setsd(spec.sd1, spec.sd2, i - j); break;
|
||||
case eSpecType::SDF_TIMES: setsd(spec.sd1, spec.sd2, i * j); break;
|
||||
case eSpecType::SDF_DIVIDE:
|
||||
setsd(spec.sd1, spec.sd2, i / j);
|
||||
setsd(spec.ex1c, spec.ex2c, i % j);
|
||||
break;
|
||||
case eSpecType::SDF_POWER:
|
||||
if(i == 2) setsd(spec.sd1, spec.sd2, 1 << j);
|
||||
else setsd(spec.sd1, spec.sd2, pow(i, j));
|
||||
break;
|
||||
default: // Unreachable case
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case eSpecType::PRINT_NUMS:
|
||||
if(!in_scen_debug) break;
|
||||
check_mess = false;
|
||||
get_strs(str1,str2, cur_spec_type,cur_node.m1 + mess_adj[cur_spec_type],
|
||||
cur_node.m2 + mess_adj[cur_spec_type]);
|
||||
if(cur_node.m1 >= 0)
|
||||
ASB("debug: " + str1, 7);
|
||||
if(cur_node.m2 >= 0)
|
||||
ASB("debug: " + str2, 7);
|
||||
// TODO: Give more options?
|
||||
switch(spec.pic) {
|
||||
case 0: // Print SDF contents
|
||||
print_nums(spec.sd1, spec.sd2, univ.party.stuff_done[spec.sd1][spec.sd2]);
|
||||
break;
|
||||
case 1: // Print three literal values (which might be pointers!)
|
||||
print_nums(spec.ex1a, spec.ex1b, spec.ex1c);
|
||||
break;
|
||||
case 2: // Print monster health and spell points
|
||||
if(spec.ex1a >= univ.town->max_monst()) break;
|
||||
print_nums(spec.ex1a, univ.town.monst[spec.ex1a].health, univ.town.monst[spec.ex1a].mp);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (check_mess == true) {
|
||||
handle_message(which_mode,cur_spec_type,cur_node.m1,cur_node.m2,a,b);
|
||||
@@ -3028,6 +3101,62 @@ void ifthen_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
|
||||
else if (k == 1)
|
||||
*next_spec = spec.ex2b;
|
||||
break;
|
||||
/* This is a little complicated.
|
||||
m1 - points to a prompt string
|
||||
m2,m3 - If nonequal, specifies a range of allowed responses.
|
||||
pic - Comparison mode: 0 - in range, 1 - not in range, 2 - simple compare
|
||||
pictype - Special to jump to if both tests pass
|
||||
ex1a, ex1b, ex1c - Test 1 values (see below)
|
||||
ex2a, ex2b, ex2c - Test 2 values (see below)
|
||||
Test values:
|
||||
If pic = 0 or 1:
|
||||
ex#a - Lower bound
|
||||
ex#b - Upper bound
|
||||
Enabled if ex#a < ex#b. If ex#a >= ex#b, this test is ignored.
|
||||
If pic = 2:
|
||||
ex#a - Value to compare to.
|
||||
ex#b - Set to 0 to enable. If -1, this test is ignored.
|
||||
ex#c - Special to jump to if test # succeeds but the other test fails.
|
||||
jumpto - Special to jump to if both tests fail.
|
||||
*/
|
||||
case eSpecType::IF_NUM_RESPONSE:
|
||||
check_mess = false;
|
||||
if(spec.m2 > spec.m3) std::swap(spec.m2,spec.m3);
|
||||
get_strs(str1,str1,0,spec.m1,-1);
|
||||
i = get_num_response(spec.m2,spec.m3,str1);
|
||||
setsd(spec.sd1, spec.sd2, abs(i));
|
||||
j = 0;
|
||||
spec.pic = minmax(0,2,spec.pic);
|
||||
switch(spec.pic) { // Comparison mode
|
||||
case 0: // Is in range?
|
||||
if(spec.ex1a < spec.ex1b && i == minmax(spec.ex1a,spec.ex1b,i)) j += 1;
|
||||
if(spec.ex2a < spec.ex2b && i == minmax(spec.ex2a,spec.ex2b,i)) j += 2;
|
||||
break;
|
||||
case 1: // Not in range?
|
||||
if(spec.ex1a < spec.ex1b && i != minmax(spec.ex1a,spec.ex1b,i)) j += 1;
|
||||
if(spec.ex2a < spec.ex2b && i != minmax(spec.ex2a,spec.ex2b,i)) j += 2;
|
||||
break;
|
||||
case 2: // Simple comparison?
|
||||
switch(spec.ex1b) {
|
||||
case -2: if(i <= spec.ex1a) j += 1; break;
|
||||
case -1: if(i < spec.ex1a) j += 1; break;
|
||||
case 0: if(i == spec.ex1a) j += 1; break;
|
||||
case 1: if(i > spec.ex1a) j += 1; break;
|
||||
case 2: if(i >= spec.ex1a) j += 1; break;
|
||||
}
|
||||
switch(spec.ex2b) {
|
||||
case -2: if(i <= spec.ex2a) j += 1; break;
|
||||
case -1: if(i < spec.ex2a) j += 1; break;
|
||||
case 0: if(i == spec.ex2a) j += 1; break;
|
||||
case 1: if(i > spec.ex2a) j += 1; break;
|
||||
case 2: if(i >= spec.ex2a) j += 1; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(j == 1) *next_spec = spec.ex1c;
|
||||
if(j == 2) *next_spec = spec.ex2c;
|
||||
if(j == 3) *next_spec = spec.pictype;
|
||||
break;
|
||||
case eSpecType::IF_SDF_EQ:
|
||||
if (sd_legit(spec.sd1,spec.sd2) == true) {
|
||||
if (PSD[spec.sd1][spec.sd2] == spec.ex1a)
|
||||
@@ -3505,6 +3634,17 @@ void townmode_spec(eSpecCtx which_mode,cSpecial cur_node,short cur_spec_type,
|
||||
else increase_light(-spec.ex2a);
|
||||
}
|
||||
break;
|
||||
case eSpecType::TOWN_SET_ATTITUDE:
|
||||
if((spec.ex1a < 0) || (spec.ex1a > 59)){
|
||||
giveError("Tried to change the attitude of a nonexistent monster (should be 0...59).");
|
||||
break;
|
||||
}
|
||||
if((spec.ex1b < 0) || (spec.ex1b > 3)){
|
||||
giveError("Invalid attitude (0-Friendly Docile, 1-Hostile A, 2-Friendly Will Fight, 3-Hostile B).");
|
||||
break;
|
||||
}
|
||||
univ.town.monst[spec.ex1a].attitude = spec.ex1b;
|
||||
break;
|
||||
}
|
||||
if (check_mess == true) {
|
||||
handle_message(which_mode,cur_spec_type,cur_node.m1,cur_node.m2,a,b);
|
||||
|
@@ -554,10 +554,10 @@ enum class eSpecType {
|
||||
SECRET_PASSAGE = 4,
|
||||
DISPLAY_SM_MSG = 5,
|
||||
FLIP_SDF = 6,
|
||||
UNUSED1 = 7, // formerly OUT_BLOCK
|
||||
UNUSED2 = 8, // formerly TOWN_BLOCK
|
||||
UNUSED3 = 9, // formerly FIGHT_BLOCK
|
||||
UNUSED4 = 10, // formerly LOOK_BLOCK
|
||||
SDF_RANDOM = 7, // formerly OUT_BLOCK
|
||||
SDF_ADD = 8, // formerly TOWN_BLOCK
|
||||
SDF_DIFF = 9, // formerly FIGHT_BLOCK
|
||||
UNUSED1 = 10, // formerly LOOK_BLOCK
|
||||
CANT_ENTER = 11,
|
||||
CHANGE_TIME = 12,
|
||||
SCEN_TIMER_START = 13,
|
||||
@@ -571,12 +571,16 @@ enum class eSpecType {
|
||||
CALL_GLOBAL = 21,
|
||||
SET_SDF_ROW = 22,
|
||||
COPY_SDF = 23,
|
||||
UNUSED6 = 24, // formerly SANCTIFY
|
||||
DISPLAY_PICTURE = 24, // formerly SANCTIFY
|
||||
REST = 25,
|
||||
WANDERING_WILL_FIGHT = 26,
|
||||
END_SCENARIO = 27,
|
||||
SET_POINTER = 28,
|
||||
SET_CAMP_FLAG = 29,
|
||||
PRINT_NUMS = 30, // For debugging
|
||||
SDF_TIMES = 31,
|
||||
SDF_DIVIDE = 32, // Computes both quotient and remainder
|
||||
SDF_POWER = 33,
|
||||
ONCE_GIVE_ITEM = 50,
|
||||
ONCE_GIVE_SPEC_ITEM = 51,
|
||||
ONCE_NULL = 52,
|
||||
@@ -645,6 +649,7 @@ enum class eSpecType {
|
||||
IF_TEXT_RESPONSE = 154,
|
||||
IF_SDF_EQ = 155,
|
||||
IF_CONTEXT = 156,
|
||||
IF_NUM_RESPONSE = 157,
|
||||
MAKE_TOWN_HOSTILE = 170,
|
||||
TOWN_CHANGE_TER = 171,
|
||||
TOWN_SWAP_TER = 172,
|
||||
@@ -672,6 +677,7 @@ enum class eSpecType {
|
||||
TOWN_REUNITE_PARTY = 194,
|
||||
TOWN_TIMER_START = 195,
|
||||
TOWN_CHANGE_LIGHTING = 196,
|
||||
TOWN_SET_ATTITUDE = 197,
|
||||
RECT_PLACE_FIRE = 200,
|
||||
RECT_PLACE_FORCE = 201,
|
||||
RECT_PLACE_ICE = 202,
|
||||
@@ -705,15 +711,15 @@ enum class eSpecCat {
|
||||
|
||||
inline eSpecCat getNodeCategory(eSpecType node) {
|
||||
int code = (int) node;
|
||||
if(code >= 0 && code <= 29)
|
||||
if(code >= 0 && code <= 33)
|
||||
return eSpecCat::GENERAL;
|
||||
if(code >= 50 && code <= 63)
|
||||
return eSpecCat::ONCE;
|
||||
if(code >= 80 && code <= 106)
|
||||
return eSpecCat::AFFECT;
|
||||
if(code >= 130 && code <= 156)
|
||||
if(code >= 130 && code <= 157)
|
||||
return eSpecCat::IF_THEN;
|
||||
if(code >= 170 && code <= 196)
|
||||
if(code >= 170 && code <= 197)
|
||||
return eSpecCat::TOWN;
|
||||
if(code >= 200 && code <= 218)
|
||||
return eSpecCat::RECT;
|
||||
|
@@ -11,6 +11,7 @@
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
#include "dlogutil.h"
|
||||
#include "classes.h"
|
||||
#include "oldstructs.h"
|
||||
|
||||
@@ -80,13 +81,41 @@ cSpecial& cSpecial::operator = (legacy::special_node_type& old){
|
||||
break;
|
||||
case 153: // if enough mage lore
|
||||
type = eSpecType::IF_STATISTIC;
|
||||
ex2a = 11;
|
||||
if(ex2a >= 0) { // Windows version added "if statistic" much earlier, but it still needs a little conversion.
|
||||
switch(ex2a) {
|
||||
case 20: ex2a = 19; break; // Max HP
|
||||
case 22: ex2a = 20; break; // Max SP
|
||||
case 19: ex2a = 100; break; // Current HP
|
||||
case 21: ex2a = 101; break; // Current SP
|
||||
case 23: ex2a = 102; break; // Experience
|
||||
case 24: ex2a = 103; break; // Skill points
|
||||
case 25: ex2a = 104; break; // Level
|
||||
}
|
||||
} else ex2a = 11;
|
||||
ex2b = 0;
|
||||
break;
|
||||
case 229: // Outdoor store - fix spell IDs
|
||||
if(ex1b == 1 || ex1b == 2)
|
||||
ex1a += 30;
|
||||
break;
|
||||
// These are ones that were added in the Windows version but only recently added to the Mac version.
|
||||
case 28:
|
||||
type = eSpecType::DISPLAY_PICTURE;
|
||||
giveError("Warning: This scenario contains a Display Picture special node created by the 'Classic Windows' version of the game. Although this version of the game also supports a Display Picture node, the format is incompatible, and automatic conversion is impossible.", "If this is not your scenario, consider contacting the scenario designer to get this fixed.");
|
||||
ex1a = 0;
|
||||
break;
|
||||
case 29:
|
||||
type = eSpecType::SDF_RANDOM;
|
||||
break;
|
||||
case 156:
|
||||
type = eSpecType::IF_SPECIES;
|
||||
break;
|
||||
case 196:
|
||||
type = eSpecType::TOWN_CHANGE_LIGHTING;
|
||||
break;
|
||||
case 197:
|
||||
type = eSpecType::TOWN_SET_ATTITUDE;
|
||||
break;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
@@ -443,6 +443,60 @@ void cPict::advanceAnim() {
|
||||
if(animFrame >= 256) animFrame = 0;
|
||||
}
|
||||
|
||||
void cPict::recalcRect() {
|
||||
RECT bounds = getBounds();
|
||||
switch(picType) {
|
||||
case NUM_PIC_TYPES: break;
|
||||
case PIC_TER: case PIC_CUSTOM_TER:
|
||||
case PIC_TER_ANIM: case PIC_CUSTOM_TER_ANIM:
|
||||
case PIC_MONST: case PIC_CUSTOM_MONST: case PIC_PARTY_MONST:
|
||||
case PIC_MONST_WIDE: case PIC_CUSTOM_MONST_WIDE: case PIC_PARTY_MONST_WIDE:
|
||||
case PIC_MONST_TALL: case PIC_CUSTOM_MONST_TALL: case PIC_PARTY_MONST_TALL:
|
||||
case PIC_MONST_LG: case PIC_CUSTOM_MONST_LG: case PIC_PARTY_MONST_LG:
|
||||
case PIC_ITEM: case PIC_CUSTOM_ITEM: case PIC_PARTY_ITEM:
|
||||
case PIC_PC: case PIC_PARTY_PC:
|
||||
case PIC_FIELD:
|
||||
case PIC_BOOM:
|
||||
bounds.width() = 28;
|
||||
bounds.height() = 36;
|
||||
break;
|
||||
case PIC_DLOG: case PIC_CUSTOM_DLOG:
|
||||
bounds.width() = 36;
|
||||
bounds.height() = 36;
|
||||
break;
|
||||
case PIC_TALK: case PIC_CUSTOM_TALK:
|
||||
case PIC_SCEN: case PIC_CUSTOM_SCEN: case PIC_PARTY_SCEN:
|
||||
bounds.width() = 32;
|
||||
bounds.height() = 32;
|
||||
break;
|
||||
case PIC_MISSILE: case PIC_CUSTOM_MISSILE:
|
||||
bounds.width() = 18;
|
||||
bounds.height() = 18;
|
||||
break;
|
||||
case PIC_DLOG_LG: case PIC_CUSTOM_DLOG_LG:
|
||||
bounds.width() = 72;
|
||||
bounds.height() = 72;
|
||||
break;
|
||||
case PIC_SCEN_LG:
|
||||
bounds.width() = 64;
|
||||
bounds.height() = 64;
|
||||
break;
|
||||
case PIC_TER_MAP: case PIC_CUSTOM_TER_MAP:
|
||||
case PIC_STATUS:
|
||||
bounds.width() = 12;
|
||||
bounds.height() = 12;
|
||||
break;
|
||||
case PIC_FULL:
|
||||
case PIC_CUSTOM_FULL:
|
||||
auto sheet = getSheet(SHEET_FULL, picNum);
|
||||
sf::Vector2u sz = sheet->getSize();
|
||||
bounds.width() = sz.x;
|
||||
bounds.height() = sz.y;
|
||||
break;
|
||||
}
|
||||
setBounds(bounds);
|
||||
}
|
||||
|
||||
std::shared_ptr<sf::Texture> cPict::getSheet(eSheetType type, size_t n) {
|
||||
std::ostringstream sout;
|
||||
switch(type) {
|
||||
@@ -525,6 +579,9 @@ std::shared_ptr<sf::Texture> cPict::getSheet(eSheetType type, size_t n) {
|
||||
case 1500:
|
||||
sout << "staticonhelp";
|
||||
break;
|
||||
default:
|
||||
// TODO: The scenario should be allowed to define a sheet1100.png without it being ignored in favour of invenhelp.png
|
||||
sout << "sheet" << n;
|
||||
}
|
||||
}
|
||||
std::shared_ptr<sf::Texture> sheet(new sf::Texture);
|
||||
|
@@ -131,6 +131,8 @@ public:
|
||||
/// Set the pict's icon.
|
||||
/// @param num The new icon index.
|
||||
void setPict(pic_num_t num);
|
||||
/// Automatically recalculate the icon's bounding rect based on its current picture.
|
||||
void recalcRect();
|
||||
/// Get the current icon.
|
||||
/// @return The number of the current icon.
|
||||
pic_num_t getPicNum();
|
||||
|
Reference in New Issue
Block a user