diff --git a/src/sc_man_scanner.re b/src/sc_man_scanner.re index cc051b05f1..4a838f89a0 100644 --- a/src/sc_man_scanner.re +++ b/src/sc_man_scanner.re @@ -191,6 +191,8 @@ std2: 'frandom' { RET(TK_FRandom); } 'randompick' { RET(TK_RandomPick); } 'frandompick' { RET(TK_FRandomPick); } + 'min' { RET(TK_Min); } + 'max' { RET(TK_Max); } L (L|D)* { RET(TK_Identifier); } diff --git a/src/sc_man_tokens.h b/src/sc_man_tokens.h index 1665a1caec..4dcddd3326 100644 --- a/src/sc_man_tokens.h +++ b/src/sc_man_tokens.h @@ -128,6 +128,8 @@ xx(TK_SizeOf, "'sizeof'") xx(TK_AlignOf, "'alignof'") xx(TK_RandomPick, "'randompick'") xx(TK_FRandomPick, "'frandompick'") +xx(TK_Min, "'min'") +xx(TK_Max, "'max'") xx(TK_States, "'states'") xx(TK_Loop, "'loop'") xx(TK_Fail, "'fail'") diff --git a/src/thingdef/thingdef_exp.cpp b/src/thingdef/thingdef_exp.cpp index 17d97e4fa8..2204a56527 100644 --- a/src/thingdef/thingdef_exp.cpp +++ b/src/thingdef/thingdef_exp.cpp @@ -349,6 +349,21 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls) // a cheap way to get them working when people use "name" instead of 'name'. return new FxConstant(FName(sc.String), scpos); } + else if (sc.CheckToken(TK_Min) || sc.CheckToken(TK_Max)) + { + int type = sc.TokenType; + TArray list; + sc.MustGetToken('('); + for (;;) + { + FxExpression *expr = ParseExpressionM(sc, cls); + list.Push(expr); + if (sc.CheckToken(')')) + break; + sc.MustGetToken(','); + } + return new FxMinMax(list, type, sc); + } else if (sc.CheckToken(TK_Random)) { FRandom *rng; diff --git a/src/thingdef/thingdef_exp.h b/src/thingdef/thingdef_exp.h index 0802c08f87..330caf0a5f 100644 --- a/src/thingdef/thingdef_exp.h +++ b/src/thingdef/thingdef_exp.h @@ -629,6 +629,24 @@ public: // //========================================================================== +class FxMinMax : public FxExpression +{ + TDeletingArray choices; + int Type; + +public: + FxMinMax(TArray &expr, int type, const FScriptPosition &pos); + FxExpression *Resolve(FCompileContext&); + + ExpEmit Emit(VMFunctionBuilder *build); +}; + +//========================================================================== +// +// +// +//========================================================================== + class FxRandom : public FxExpression { protected: diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index f9ab32b730..f5c123f4d5 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -2038,6 +2038,214 @@ ExpEmit FxAbs::Emit(VMFunctionBuilder *build) return out; } +//========================================================================== +// +// +// +//========================================================================== +FxMinMax::FxMinMax(TArray &expr, int type, const FScriptPosition &pos) +: FxExpression(pos), Type(type) +{ + assert(expr.Size() > 0); + assert(type == TK_Min || type == TK_Max); + + ValueType = VAL_Unknown; + choices.Resize(expr.Size()); + for (unsigned i = 0; i < expr.Size(); ++i) + { + choices[i] = expr[i]; + } +} + +//========================================================================== +// +// +// +//========================================================================== +FxExpression *FxMinMax::Resolve(FCompileContext &ctx) +{ + unsigned int i; + bool isconst; + int intcount, floatcount; + + CHECKRESOLVED(); + + // Determine if float or int + intcount = floatcount = 0; + for (i = 0; i < choices.Size(); ++i) + { + RESOLVE(choices[i], ctx); + ABORT(choices[i]); + + if (choices[i]->ValueType == VAL_Float) + { + floatcount++; + } + else if (choices[i]->ValueType == VAL_Int) + { + intcount++; + } + else + { + ScriptPosition.Message(MSG_ERROR, "Arguments must be of type int or float"); + delete this; + return NULL; + } + } + if (floatcount != 0) + { + ValueType = VAL_Float; + if (intcount != 0) + { // There are some ints that need to be cast to floats + for (i = 0; i < choices.Size(); ++i) + { + if (choices[i]->ValueType == VAL_Int) + { + choices[i] = new FxFloatCast(choices[i]); + RESOLVE(choices[i], ctx); + ABORT(choices[i]); + } + } + } + } + else + { + ValueType = VAL_Int; + } + + // Determine if every argument is constant + isconst = true; + for (i = 0; i < choices.Size(); ++i) + { + if (!choices[i]->isConstant()) + { + isconst = false; + break; + } + } + + // If every argument is constant, we can decide this now. + if (isconst) + { + ExpVal best = static_cast(choices[0])->GetValue(); + for (i = 1; i < choices.Size(); ++i) + { + ExpVal value = static_cast(choices[i])->GetValue(); + assert(value.Type == ValueType.Type); + if (Type == TK_Min) + { + if (value.Type == VAL_Float) + { + if (value.Float < best.Float) + { + best.Float = value.Float; + } + } + else + { + if (value.Int < best.Int) + { + best.Int = value.Int; + } + } + } + else + { + if (value.Type == VAL_Float) + { + if (value.Float > best.Float) + { + best.Float = value.Float; + } + } + else + { + if (value.Int > best.Int) + { + best.Int = value.Int; + } + } + } + } + FxExpression *x = new FxConstant(best, ScriptPosition); + delete this; + return x; + } + return this; +} + +//========================================================================== +// +// +// +//========================================================================== +static void EmitLoad(VMFunctionBuilder *build, const ExpEmit resultreg, const ExpVal &value) +{ + if (resultreg.RegType == REGT_FLOAT) + { + build->Emit(OP_LKF, resultreg.RegNum, build->GetConstantFloat(value.GetFloat())); + } + else + { + build->EmitLoadInt(resultreg.RegNum, value.GetInt()); + } +} + +ExpEmit FxMinMax::Emit(VMFunctionBuilder *build) +{ + unsigned i; + int opcode, opA; + + assert(choices.Size() > 0); + assert(OP_LTF_RK == OP_LTF_RR+1); + assert(OP_LT_RK == OP_LT_RR+1); + assert(OP_LEF_RK == OP_LEF_RR+1); + assert(OP_LE_RK == OP_LE_RR+1); + + if (Type == TK_Min) + { + opcode = ValueType.Type == VAL_Float ? OP_LEF_RR : OP_LE_RR; + opA = 1; + } + else + { + opcode = ValueType.Type == VAL_Float ? OP_LTF_RR : OP_LT_RR; + opA = 0; + } + + ExpEmit bestreg; + + // Get first value into a register. This will also be the result register. + if (choices[0]->isConstant()) + { + bestreg = ExpEmit(build, ValueType.Type == VAL_Float ? REGT_FLOAT : REGT_INT); + EmitLoad(build, bestreg, static_cast(choices[0])->GetValue()); + } + else + { + bestreg = choices[0]->Emit(build); + } + + // Compare every choice. Better matches get copied to the bestreg. + for (i = 1; i < choices.Size(); ++i) + { + ExpEmit checkreg = choices[i]->Emit(build); + assert(checkreg.RegType == bestreg.RegType); + build->Emit(opcode + checkreg.Konst, opA, bestreg.RegNum, checkreg.RegNum); + build->Emit(OP_JMP, 1); + if (checkreg.Konst) + { + build->Emit(bestreg.RegType == REGT_FLOAT ? OP_LKF : OP_LK, bestreg.RegNum, checkreg.RegNum); + } + else + { + build->Emit(bestreg.RegType == REGT_FLOAT ? OP_MOVEF : OP_MOVE, bestreg.RegNum, checkreg.RegNum, 0); + checkreg.Free(build); + } + } + return bestreg; +} + //========================================================================== // // @@ -2269,16 +2477,7 @@ ExpEmit FxRandomPick::Emit(VMFunctionBuilder *build) build->BackpatchToHere(jumptable + i); if (choices[i]->isConstant()) { - if (ValueType == VAL_Int) - { - int val = static_cast(choices[i])->GetValue().GetInt(); - build->EmitLoadInt(resultreg.RegNum, val); - } - else - { - double val = static_cast(choices[i])->GetValue().GetFloat(); - build->Emit(OP_LKF, resultreg.RegNum, build->GetConstantFloat(val)); - } + EmitLoad(build, resultreg, static_cast(choices[i])->GetValue()); } else { diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index 2425ce6947..642269f283 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -311,8 +311,8 @@ ACTOR Actor native //: Thinker action native state A_JumpIfHigherOrLower(state high, state low, float offsethigh = 0, float offsetlow = 0, bool includeHeight = true, int ptr = AAPTR_TARGET); action native A_SetSpecies(name species, int ptr = AAPTR_DEFAULT); action native A_SetRipperLevel(int level); - action native A_SetRipMin(int min); - action native A_SetRipMax(int max); + action native A_SetRipMin(int mininum); + action native A_SetRipMax(int maximum); action native A_SetChaseThreshold(int threshold, bool def = false, int ptr = AAPTR_DEFAULT); action native state A_CheckProximity(state jump, class classname, float distance, int count = 1, int flags = 0, int ptr = AAPTR_DEFAULT); action native state A_CheckBlock(state block, int flags = 0, int ptr = AAPTR_DEFAULT);