Procházet zdrojové kódy

Refactored achievement conditions (#3831)

They now use the normal script engine and not a duplicated portion of code of it.
This is required for another pending update.

This also fixes atcommands not triggering status achievements.

Thanks to @aleos89 and @RadianFord
Lemongrass3110 před 6 roky
rodič
revize
4706115d5b

+ 12 - 17
db/pre-re/achievement_db.yml

@@ -1690,72 +1690,72 @@ Achievements:
   - ID: 200017
     Group: "AG_GOAL_STATUS"
     Name: "Bearish Power!"
-    Condition: " bStr >= 90 "
+    Condition: " readparam(bStr) >= 90 "
     Score: 10
   - ID: 200018
     Group: "AG_GOAL_STATUS"
     Name: "Overflowing Magic!"
-    Condition: " bInt >= 90 "
+    Condition: " readparam(bInt) >= 90 "
     Score: 10
   - ID: 200019
     Group: "AG_GOAL_STATUS"
     Name: "Healthy Body and Mental Health!"
-    Condition: " bVit >= 90 "
+    Condition: " readparam(bVit) >= 90 "
     Score: 10
   - ID: 200020
     Group: "AG_GOAL_STATUS"
     Name: "Speed of Light"
-    Condition: " bAgi >= 90 "
+    Condition: " readparam(bAgi) >= 90 "
     Score: 10
   - ID: 200021
     Group: "AG_GOAL_STATUS"
     Name: "Hawk Eyes"
-    Condition: " bDex >= 90 "
+    Condition: " readparam(bDex) >= 90 "
     Score: 10
   - ID: 200022
     Group: "AG_GOAL_STATUS"
     Name: "Maximum Luck"
-    Condition: " bLuk >= 90 "
+    Condition: " readparam(bLuk) >= 90 "
     Score: 10
   - ID: 200023
     Group: "AG_GOAL_STATUS"
     Name: "Dragonlike Power!"
-    Condition: " bStr >= 125 "
+    Condition: " readparam(bStr) >= 125 "
     Reward:
       Script: " sc_start SC_GIANTGROWTH,180000,1; "
     Score: 20
   - ID: 200024
     Group: "AG_GOAL_STATUS"
     Name: "Magic Insanity"
-    Condition: " bInt >= 125 "
+    Condition: " readparam(bInt) >= 125 "
     Reward:
       Script: " specialeffect2 EF_HASTEUP; bonus_script \"{ bonus2 bHPLossRate,100,10000; bonus bBaseAtk,20; bonus bAspdRate,25; }\",60,0,0,EFST_STEAMPACK; "
     Score: 20
   - ID: 200025
     Group: "AG_GOAL_STATUS"
     Name: "Rock Alloy"
-    Condition: " bVit >= 125 "
+    Condition: " readparam(bVit) >= 125 "
     Reward:
       Script: " specialeffect2 EF_HEAL3; sc_start2 SC_S_LIFEPOTION,600000,-5,5; "
     Score: 20
   - ID: 200026
     Group: "AG_GOAL_STATUS"
     Name: "Speed of Light"
-    Condition: " bAgi >= 125 "
+    Condition: " readparam(bAgi) >= 125 "
     Reward:
       Script: " specialeffect2 EF_STEAL; sc_start SC_INCFLEE2,60000,20; "
     Score: 20
   - ID: 200027
     Group: "AG_GOAL_STATUS"
     Name: "Falcon's Eyes"
-    Condition: " bDex >= 125 "
+    Condition: " readparam(bDex) >= 125 "
     Reward:
       Script: " specialeffect2 EF_MAGICALATTHIT; sc_start SC_INCCRI,300000,30; "
     Score: 20
   - ID: 200028
     Group: "AG_GOAL_STATUS"
     Name: "Lucky Fever"
-    Condition: " bLuk >= 125 "
+    Condition: " readparam(bLuk) >= 125 "
     Reward:
       Script: " specialeffect2 EF_GLORIA; sc_start SC_GLORIA,15000,0; "
     Score: 20
@@ -2000,12 +2000,10 @@ Achievements:
   - ID: 220000
     Group: "AG_CHATTING_CREATE"
     Name: "Community begin"
-    Condition: " true "
     Score: 10
   - ID: 220001
     Group: "AG_CHATTING_DYING"
     Name: "A mouth only moment"
-    Condition: " true "
     Score: 10
   - ID: 220002
     Group: "AG_CHATTING_COUNT"
@@ -2025,12 +2023,10 @@ Achievements:
   - ID: 220005
     Group: "AG_PARTY"
     Name: "Let's Party~"
-    Condition: " true "
     Score: 10
   - ID: 220006
     Group: "AG_MARRY"
     Name: "Married with who..?"
-    Condition: " true "
     Reward:
       TitleID: 1022
     Score: 20
@@ -2126,7 +2122,6 @@ Achievements:
   - ID: 220022
     Group: "AG_ENCHANT_FAIL"
     Name: "Human's greed has no ending.."
-    Condition: " true "
     Score: 10
   - ID: 220023
     Group: "AG_GET_ITEM"

+ 12 - 17
db/re/achievement_db.yml

@@ -1690,72 +1690,72 @@ Achievements:
   - ID: 200017
     Group: "AG_GOAL_STATUS"
     Name: "Bearish Power!"
-    Condition: " bStr >= 90 "
+    Condition: " readparam(bStr) >= 90 "
     Score: 10
   - ID: 200018
     Group: "AG_GOAL_STATUS"
     Name: "Overflowing Magic!"
-    Condition: " bInt >= 90 "
+    Condition: " readparam(bInt) >= 90 "
     Score: 10
   - ID: 200019
     Group: "AG_GOAL_STATUS"
     Name: "Healthy Body and Mental Health!"
-    Condition: " bVit >= 90 "
+    Condition: " readparam(bVit) >= 90 "
     Score: 10
   - ID: 200020
     Group: "AG_GOAL_STATUS"
     Name: "Speed of Light"
-    Condition: " bAgi >= 90 "
+    Condition: " readparam(bAgi) >= 90 "
     Score: 10
   - ID: 200021
     Group: "AG_GOAL_STATUS"
     Name: "Hawk Eyes"
-    Condition: " bDex >= 90 "
+    Condition: " readparam(bDex) >= 90 "
     Score: 10
   - ID: 200022
     Group: "AG_GOAL_STATUS"
     Name: "Maximum Luck"
-    Condition: " bLuk >= 90 "
+    Condition: " readparam(bLuk) >= 90 "
     Score: 10
   - ID: 200023
     Group: "AG_GOAL_STATUS"
     Name: "Dragonlike Power!"
-    Condition: " bStr >= 125 "
+    Condition: " readparam(bStr) >= 125 "
     Reward:
       Script: " sc_start SC_GIANTGROWTH,180000,1; "
     Score: 20
   - ID: 200024
     Group: "AG_GOAL_STATUS"
     Name: "Magic Insanity"
-    Condition: " bInt >= 125 "
+    Condition: " readparam(bInt) >= 125 "
     Reward:
       Script: " specialeffect2 EF_HASTEUP; bonus_script \"{ bonus2 bHPLossRate,100,10000; bonus bBaseAtk,20; bonus bAspdRate,25; }\",60,0,0,EFST_STEAMPACK; "
     Score: 20
   - ID: 200025
     Group: "AG_GOAL_STATUS"
     Name: "Rock Alloy"
-    Condition: " bVit >= 125 "
+    Condition: " readparam(bVit) >= 125 "
     Reward:
       Script: " specialeffect2 EF_HEAL3; sc_start2 SC_S_LIFEPOTION,600000,-5,5; "
     Score: 20
   - ID: 200026
     Group: "AG_GOAL_STATUS"
     Name: "Speed of Light"
-    Condition: " bAgi >= 125 "
+    Condition: " readparam(bAgi) >= 125 "
     Reward:
       Script: " specialeffect2 EF_STEAL; sc_start SC_INCFLEE2,60000,20; "
     Score: 20
   - ID: 200027
     Group: "AG_GOAL_STATUS"
     Name: "Falcon's Eyes"
-    Condition: " bDex >= 125 "
+    Condition: " readparam(bDex) >= 125 "
     Reward:
       Script: " specialeffect2 EF_MAGICALATTHIT; sc_start SC_INCCRI,300000,30; "
     Score: 20
   - ID: 200028
     Group: "AG_GOAL_STATUS"
     Name: "Lucky Fever"
-    Condition: " bLuk >= 125 "
+    Condition: " readparam(bLuk) >= 125 "
     Reward:
       Script: " specialeffect2 EF_GLORIA; sc_start SC_GLORIA,15000,0; "
     Score: 20
@@ -2000,12 +2000,10 @@ Achievements:
   - ID: 220000
     Group: "AG_CHATTING_CREATE"
     Name: "Community begin"
-    Condition: " true "
     Score: 10
   - ID: 220001
     Group: "AG_CHATTING_DYING"
     Name: "A mouth only moment"
-    Condition: " true "
     Score: 10
   - ID: 220002
     Group: "AG_CHATTING_COUNT"
@@ -2025,12 +2023,10 @@ Achievements:
   - ID: 220005
     Group: "AG_PARTY"
     Name: "Let's Party~"
-    Condition: " true "
     Score: 10
   - ID: 220006
     Group: "AG_MARRY"
     Name: "Married with who..?"
-    Condition: " true "
     Reward:
       TitleID: 1022
     Score: 20
@@ -2126,7 +2122,6 @@ Achievements:
   - ID: 220022
     Group: "AG_ENCHANT_FAIL"
     Name: "Human's greed has no ending.."
-    Condition: " true "
     Score: 10
   - ID: 220023
     Group: "AG_GET_ITEM"

+ 60 - 317
src/map/achievement.cpp

@@ -28,11 +28,6 @@
 #include "script.hpp"
 #include "status.hpp"
 
-static jmp_buf     av_error_jump;
-static char*       av_error_msg;
-static const char* av_error_pos;
-static int         av_error_report;
-
 std::unordered_map<int, std::shared_ptr<s_achievement_db>> achievements;
 std::vector<int> achievement_mobs; // Avoids checking achievements on every mob killed
 
@@ -502,6 +497,40 @@ int *achievement_level(struct map_session_data *sd, bool flag)
 	return info;
 }
 
+static bool achievement_check_condition( struct script_code* condition, struct map_session_data* sd, const int* count ){
+	// Save the old script the player was attached to
+	struct script_state* previous_st = sd->st;
+
+	// Only if there was an old script
+	if( previous_st != nullptr ){
+		// Detach the player from the current script
+		script_detach_rid(previous_st);
+	}
+
+	run_script( condition, 0, sd->bl.id, fake_nd->bl.id );
+
+	struct script_state* st = sd->st;
+
+	int value = 0;
+
+	if( st != nullptr ){
+		value = script_getnum( st, 2 );
+
+		script_free_state(st);
+	}
+
+	// If an old script is present
+	if( previous_st != nullptr ){
+		// Because of detach the RID will be removed, so we need to restore it
+		previous_st->rid = sd->bl.id;
+
+		// Reattach the player to it, so that the limitations of that script kick back in
+		script_attach_state( previous_st );
+	}
+
+	return value != 0;
+}
+
 /**
  * Update achievement objectives.
  * @param sd: Player to update
@@ -657,8 +686,13 @@ void achievement_update_objective(struct map_session_data *sd, enum e_achievemen
 		count.fill(0); // Clear out array before setting values
 
 		va_start(ap, arg_count);
-		for (i = 0; i < arg_count; i++)
+		for (i = 0; i < arg_count; i++){
+			std::string name = "ARG" + std::to_string(i);
+
 			count[i] = va_arg(ap, int);
+
+			pc_setglobalreg( sd, add_str( name.c_str() ), (int)count[i] );
+		}
 		va_end(ap);
 
 		switch(group) {
@@ -671,309 +705,14 @@ void achievement_update_objective(struct map_session_data *sd, enum e_achievemen
 					achievement_update_objectives(sd, ach.second, group, count);
 				break;
 		}
-	}
-}
-
-/*==========================================
- * Achievement condition parsing section
- *------------------------------------------*/
-static void disp_error_message2(const char *mes,const char *pos,int report)
-{
-	av_error_msg = aStrdup(mes);
-	av_error_pos = pos;
-	av_error_report = report;
-	longjmp(av_error_jump, 1);
-}
-#define disp_error_message(mes,pos) disp_error_message2(mes,pos,1)
-
-/**
- * Checks the condition of an achievement.
- * @param condition: Achievement condition
- * @param sd: Player data
- * @param count: Script arguments
- * @return The result of the condition.
- */
-long long achievement_check_condition(std::shared_ptr<struct av_condition> condition, struct map_session_data *sd, const int *count)
-{
-	long long left = 0;
-	long long right = 0;
-
-	// Reduce the recursion, almost all calls will be C_PARAM, C_NAME or C_ARG
-	if (condition->left) {
-		if (condition->left->op == C_NAME || condition->left->op == C_INT)
-			left = condition->left->value;
-		else if (condition->left->op == C_PARAM)
-			left = pc_readparam(sd, (int)condition->left->value);
-		else if (condition->left->op == C_ARG && condition->left->value < MAX_ACHIEVEMENT_OBJECTIVES)
-			left = count[condition->left->value];
-		else
-			left = achievement_check_condition(condition->left, sd, count);
-	}
-
-	if (condition->right) {
-		if (condition->right->op == C_NAME || condition->right->op == C_INT)
-			right = condition->right->value;
-		else if (condition->right->op == C_PARAM)
-			right = pc_readparam(sd, (int)condition->right->value);
-		else if (condition->right->op == C_ARG && condition->right->value < MAX_ACHIEVEMENT_OBJECTIVES)
-			right = count[condition->right->value];
-		else
-			right = achievement_check_condition(condition->right, sd, count);
-	}
-
-	switch(condition->op) {
-		case C_NOP:
-			return false;
-		case C_NAME:
-		case C_INT:
-			return condition->value;
-		case C_PARAM:
-			return pc_readparam(sd, (int)condition->value);
-		case C_LOR: 
-			return left || right;
-		case C_LAND:
-			return left && right;
-		case C_LE:
-			return left <= right;
-		case C_LT:
-			return left < right;
-		case C_GE:
-			return left >= right;
-		case C_GT:
-			return left > right;
-		case C_EQ:
-			return left == right;
-		case C_NE:
-			return left != right;
-		case C_XOR:
-			return left ^ right;
-		case C_OR:
-			return left || right;
-		case C_AND:
-			return left & right;
-		case C_ADD:
-			return left + right;
-		case C_SUB:
-			return left - right;
-		case C_MUL:
-			return left * right;
-		case C_DIV:
-			return left / right;
-		case C_MOD:
-			return left % right;
-		case C_NEG:
-			return -left;
-		case C_LNOT:
-			return !left;
-		case C_NOT:
-			return ~left;
-		case C_R_SHIFT:
-			return left >> right;
-		case C_L_SHIFT:
-			return left << right;
-		case C_ARG:
-			if (condition->value < MAX_ACHIEVEMENT_OBJECTIVES)
-				return count[condition->value];
-
-			return false;
-		default:
-			ShowError("achievement_check_condition: unexpected operator: %d\n", condition->op);
-			return false;
-	}
-
-	return false;
-}
-
-/**
- * Skips a word. A word consists of undercores and/or alphanumeric characters, and valid variable prefixes/postfixes.
- * @param p: Word
- * @return Next word
- */
-static const char *skip_word(const char *p)
-{
-	while (ISALNUM(*p) || *p == '_')
-		++p;
-
-	if (*p == '$') // String
-		p++;
-
-	return p;
-}
-
-/**
- * Analyze an achievement's condition script
- * @param p: Word
- * @param parent: Parent node
- * @return Word
- */
-const char *av_parse_simpleexpr(const char *p, std::shared_ptr<struct av_condition> parent)
-{
-	long long i;
-
-	p = skip_space(p);
-
-	if(*p == ';' || *p == ',')
-		disp_error_message("av_parse_simpleexpr: unexpected character.", p);
-	if(*p == '(') {
-		p = av_parse_subexpr(p + 1, -1, parent);
-		p = skip_space(p);
-
-		if (*p != ')')
-			disp_error_message("av_parse_simpleexpr: unmatched ')'", p);
-		++p;
-	} else if(is_number(p)) {
-		char *np;
-
-		while(*p == '0' && ISDIGIT(p[1]))
-			p++;
-		i = strtoll(p, &np, 0);
-
-		if (i < INT_MIN) {
-			i = INT_MIN;
-			disp_error_message("av_parse_simpleexpr: underflow detected, capping value to INT_MIN.", p);
-		} else if (i > INT_MAX) {
-			i = INT_MAX;
-			disp_error_message("av_parse_simpleexpr: underflow detected, capping value to INT_MAX.", p);
-		}
 
-		parent->op = C_INT;
-		parent->value = i;
-		p = np;
-	} else {
-		int v, len;
-
-		if (skip_word(p) == p)
-			disp_error_message("av_parse_simpleexpr: unexpected character.", p);
-
-		len = skip_word(p) - p;
-
-		if (len == 0)
-			disp_error_message("av_parse_simpleexpr: invalid word. A word consists of undercores and/or alphanumeric characters.", p);
-
-		std::unique_ptr<char[]> word(new char[len + 1]);
-		memcpy(word.get(), p, len);
-		word[len] = 0;
-
-		if (script_get_parameter((const char*)&word[0], &v))
-			parent->op = C_PARAM;
-		else if (script_get_constant(&word[0], &v)) {
-			if (word[0] == 'b' && ISUPPER(word[1])) // Consider b* variables as parameters (because they... are?)
-				parent->op = C_PARAM;
-			else
-				parent->op = C_NAME;
-		} else {
-			if (word[0] == 'A' && word[1] == 'R' && word[2] == 'G' && ISDIGIT(word[3])) { // Special constants used to set temporary variables
-				parent->op = C_ARG;
-				v = atoi(&word[0] + 3);
-			} else {
-				disp_error_message("av_parse_simpleexpr: invalid constant.", p);
-			}
-		}
+		// Remove variables that might have been set
+		for (i = 0; i < arg_count; i++){
+			std::string name = "ARG" + std::to_string(i);
 
-		parent->value = v;
-		p = skip_word(p);
-	}
-
-	return p;
-}
-
-/**
- * Analysis of an achievement's expression
- * @param p: Word
- * @param parent: Parent node
- * @return Word
- */
-const char *av_parse_subexpr(const char* p, int limit, std::shared_ptr<struct av_condition> parent)
-{
-	int op, opl, len;
-
-	p = skip_space(p);
-
-	parent->left.reset(new av_condition());
-
-	if ((op = C_NEG, *p == '-') || (op = C_LNOT, *p == '!') || (op = C_NOT, *p == '~')) { // Unary - ! ~ operators
-		p = av_parse_subexpr(p + 1, 11, parent->left);
-		parent->op = op;
-	} else
-		p = av_parse_simpleexpr(p, parent->left);
-
-	p = skip_space(p);
-
-	while((
-			((op=C_ADD,opl=9,len=1,*p=='+') && p[1]!='+') ||
-			((op=C_SUB,opl=9,len=1,*p=='-') && p[1]!='-') ||
-			(op=C_MUL,opl=10,len=1,*p=='*') ||
-			(op=C_DIV,opl=10,len=1,*p=='/') ||
-			(op=C_MOD,opl=10,len=1,*p=='%') ||
-			(op=C_LAND,opl=2,len=2,*p=='&' && p[1]=='&') ||
-			(op=C_AND,opl=5,len=1,*p=='&') ||
-			(op=C_LOR,opl=1,len=2,*p=='|' && p[1]=='|') ||
-			(op=C_OR,opl=3,len=1,*p=='|') ||
-			(op=C_XOR,opl=4,len=1,*p=='^') ||
-			(op=C_EQ,opl=6,len=2,*p=='=' && p[1]=='=') ||
-			(op=C_NE,opl=6,len=2,*p=='!' && p[1]=='=') ||
-			(op=C_R_SHIFT,opl=8,len=2,*p=='>' && p[1]=='>') ||
-			(op=C_GE,opl=7,len=2,*p=='>' && p[1]=='=') ||
-			(op=C_GT,opl=7,len=1,*p=='>') ||
-			(op=C_L_SHIFT,opl=8,len=2,*p=='<' && p[1]=='<') ||
-			(op=C_LE,opl=7,len=2,*p=='<' && p[1]=='=') ||
-			(op=C_LT,opl=7,len=1,*p=='<')) && opl>limit) {
-		p += len;
-
-		if (parent->right) { // Chain conditions
-			std::shared_ptr<struct av_condition> condition(new struct av_condition());
-
-			condition->op = parent->op;
-			condition->left = parent->left;
-			condition->right = parent->right;
-			parent->left = condition;
-			parent->right.reset();
+			pc_setglobalreg( sd, add_str( name.c_str() ), 0 );
 		}
-
-		parent->right.reset(new av_condition());
-		p = av_parse_subexpr(p, opl, parent->right);
-		parent->op = op;
-		p = skip_space(p);
-	}
-
-	if (parent->op == C_NOP && !parent->right) { // Move the node up
-		parent->right = parent->left->right;
-		parent->op = parent->left->op;
-		parent->value = parent->left->value;
-		parent->left = parent->left->left;
-	}
-
-	return p;
-}
-
-/**
- * Parses a condition from a script.
- * @param p: The script buffer.
- * @param file: The file being parsed.
- * @param line: The current achievement line number.
- * @return The parsed achievement condition.
- */
-std::shared_ptr<struct av_condition> parse_condition(const char *p, const char *file, int line)
-{
-	std::shared_ptr<struct av_condition> condition;
-
-	if (setjmp(av_error_jump) != 0) {
-		if (av_error_report)
-			script_error(p,file,line,av_error_msg,av_error_pos);
-		aFree(av_error_msg);
-		condition.reset();
-		return NULL;
-	}
-
-	switch(*p) {
-		case ')': case ';': case ':': case '[': case ']': case '}':
-			disp_error_message("parse_condition: unexpected character.", p);
 	}
-
-	condition.reset(new av_condition());
-	av_parse_subexpr(p, -1, condition);
-
-	return condition;
 }
 
 static void yaml_invalid_warning(const char* fmt, const YAML::Node &node, const std::string &file) {
@@ -1090,9 +829,14 @@ bool achievement_read_db_sub(const YAML::Node &node, int n, const std::string &s
 			yaml_invalid_warning("achievement_read_db_sub: Achievement definition with invalid condition field in '" CL_WHITE "%s" CL_RESET "', skipping.\n", node, source);
 			return false;
 		}
-		entry->condition = parse_condition(condition.c_str(), source.c_str(), n);
-	}
 
+		if( condition.find( "achievement_condition" ) == std::string::npos ){
+			condition = "achievement_condition( " + condition + " );";
+		}
+
+		entry->condition = parse_script( condition.c_str(), source.c_str(), node["Condition"].Mark().line, SCRIPT_IGNORE_EXTERNAL_BRACKETS );
+	}
+	
 	if (node["Map"]) {
 		try {
 			mapname = node["Map"].as<std::string>();
@@ -1204,16 +948,6 @@ void achievement_read_db(void)
 	return;
 }
 
-/**
- * Recursive method to free an achievement condition (probably not needed anymore, but just in case)
- * @param condition: Condition to clear
- */
-void achievement_script_free(std::shared_ptr<struct av_condition> condition) 
-{
-	condition->left.reset();
-	condition->right.reset();
-}
-
 /**
  * Reloads the achievement database
  */
@@ -1260,6 +994,15 @@ s_achievement_db::s_achievement_db()
 	, has_dependent(0)
 {}
 
+/**
+* Achievement deconstructor
+*/
+s_achievement_db::~s_achievement_db()
+{
+	if (condition)
+		script_free_code(condition);
+}
+
 /**
  * Achievement reward constructor
  */

+ 2 - 16
src/map/achievement.hpp

@@ -68,22 +68,13 @@ struct achievement_target {
 	int count;
 };
 
-struct av_condition {
-	int op;
-	std::shared_ptr<struct av_condition> left;
-	std::shared_ptr<struct av_condition> right;
-	long long value;
-
-	av_condition() : op(0), left(nullptr), right(nullptr), value(0) {}
-};
-
 struct s_achievement_db {
 	int achievement_id;
 	std::string name;
 	enum e_achievement_group group;
 	std::vector <achievement_target> targets;
 	std::vector <int> dependent_ids;
-	std::shared_ptr<struct av_condition> condition;
+	struct script_code* condition;
 	int16 mapindex;
 	struct ach_reward {
 		unsigned short nameid, amount;
@@ -96,6 +87,7 @@ struct s_achievement_db {
 	int has_dependent; // Used for quick updating of achievements that depend on others - this is their ID
 
 	s_achievement_db();
+	~s_achievement_db();
 };
 
 bool achievement_exists(int achievement_id);
@@ -117,10 +109,4 @@ void achievement_db_reload(void);
 void do_init_achievement(void);
 void do_final_achievement(void);
 
-// Parser
-const char *av_parse_subexpr(const char *p,int limit, std::shared_ptr<struct av_condition> parent);
-const char *av_parse_simpleexpr(const char *p, std::shared_ptr<struct av_condition> parent);
-long long achievement_check_condition(std::shared_ptr<struct av_condition> condition, struct map_session_data *sd, const int *count);
-void achievement_script_free(std::shared_ptr<struct av_condition> condition);
-
 #endif /* ACHIEVEMENT_HPP */

+ 4 - 0
src/map/atcommand.cpp

@@ -2610,6 +2610,8 @@ ACMD_FUNC(param)
 		clif_updatestatus(sd, SP_USTR + i);
 		status_calc_pc(sd, SCO_FORCE);
 		clif_displaymessage(fd, msg_txt(sd,42)); // Stat changed.
+
+		achievement_update_objective(sd, AG_GOAL_STATUS, 0);
 	} else {
 		if (value < 0)
 			clif_displaymessage(fd, msg_txt(sd,41)); // Unable to decrease the number/value.
@@ -2681,6 +2683,8 @@ ACMD_FUNC(stat_all)
 	if (count > 0) { // if at least 1 stat modified
 		status_calc_pc(sd, SCO_FORCE);
 		clif_displaymessage(fd, msg_txt(sd,84)); // All stats changed!
+
+		achievement_update_objective(sd, AG_GOAL_STATUS, 0);
 	} else {
 		if (value < 0)
 			clif_displaymessage(fd, msg_txt(sd,177)); // You cannot decrease that stat anymore.

+ 14 - 1
src/map/script.cpp

@@ -12197,7 +12197,7 @@ BUILDIN_FUNC(warpwaitingpc)
 /// Detaches a character from a script.
 ///
 /// @param st Script state to detach the character from.
-static void script_detach_rid(struct script_state* st)
+void script_detach_rid(struct script_state* st)
 {
 	if(st->rid)
 	{
@@ -24061,6 +24061,17 @@ BUILDIN_FUNC( camerainfo ){
 #endif
 }
 
+// This function is only meant to be used inside of achievement conditions
+BUILDIN_FUNC(achievement_condition){
+	// Push what we get from the script
+	script_pushint( st, 2 );
+
+	// Otherwise the script is freed afterwards
+	st->state = RERUNLINE;
+
+	return SCRIPT_CMD_SUCCESS;
+}
+
 #include "../custom/script.inc"
 
 // declarations that were supposed to be exported from npc_chat.c
@@ -24724,6 +24735,8 @@ struct script_function buildin_func[] = {
 	BUILDIN_DEF(is_guild_leader,"?"),
 	BUILDIN_DEF(is_party_leader,"?"),
 	BUILDIN_DEF(camerainfo,"iii?"),
+
+	BUILDIN_DEF(achievement_condition,"i"),
 #include "../custom/script_def.inc"
 
 	{NULL,NULL,NULL},

+ 1 - 0
src/map/script.hpp

@@ -1935,6 +1935,7 @@ TIMER_FUNC(run_script_timer);
 void script_stop_sleeptimers(int id);
 struct linkdb_node *script_erase_sleepdb(struct linkdb_node *n);
 void script_attach_state(struct script_state* st);
+void script_detach_rid(struct script_state* st);
 void run_script_main(struct script_state *st);
 
 void script_stop_scriptinstances(struct script_code *code);