How do I pass bind variables to Message Explain text in message catalog? - peoplesoft

I know that when I use messages from the message catalog, I can use bind variables in the text of the message. For example,
Message Text:
Employee (%1) has already worked %2% this month.
Description:
Employees cannot work over %3% for any given month. Decrease percent for row %5 to %4%.
I noticed that when I use MessageBox, I can pass a value into the third, fourth, and firth binds, but when I use any other function (MsgGet, MsgGetExplainText, or CreateException), the binds in the explain text are not replaced. I am doing this because I need sometimes stop processing with Error, passing in the text from the message catalog (i.e. Error(MsgGet()).
For example:
Using MessageBox()
Using Error(MsgGet())
These are produced from the following, respectively:
MessageBox(0, "", 20007, 6, "MESSAGE NOT FOUND: Too many hours. Decrease percentage.", &emplid, Round(&percentInMonth * 100, 0), Round(&workPercentCap * 100, 0), &neededPercent, &rowNumber);
And
Error (MsgGet(20007, 6, "MESSAGE NOT FOUND: Too many hours. Decrease percentage.", &emplid, Round(&percentInMonth * 100, 0), Round(&workPercentCap * 100, 0), &neededPercent, &rowNumber));
How do I get around this problem? I am running PeopleTools 8.54.

In the 8.54 PeopleBooks for MsgGetExplainText, it mentions the following:
Note: This substitution only takes place in message explain text when the MsgGetExplainText function is used. If you use a message box, the parameter substitution will not occur in the explain text.
This is an unclear way of saying that you can't do that. This also applies to MsgGet and CreateException, even though not specified in their docs.
However, there is a workaround found at IT Toolbox.
Users in that thread posted something close to the following code as a workaround (adapted here to suit your question):
Local string &errorMessage = MsgGet(20007, 6, "MESSAGE NOT FOUND: Too many hours. Decrease percentage.", &emplid, Round(&percentInMonth * 100, 0), Round(&workPercentCap * 100, 0), &neededPercent, 1);
&errorMessage = &errorMessage | Char(10) | Char(10);
&errorMessage = &errorMessage | MsgGetExplainText(20007, 6, "MESSAGE NOT FOUND: Too many hours. Decrease percentage.", &emplid, Round(&percentInMonth * 100, 0), Round(&workPercentCap * 100, 0), &neededPercent, 1);
Local string &temp = MsgGet(0, 0, "");
Error &errorMessage;
For whatever reason, the binding of the variables happens after the fact, meaning, if you call Error on the return value of MsgGet, the result is an error message with the unbound parameters.
You need to append the results of MsgGet and MsgGetExplainText onto each other to mimic the functionality of MessageBox as above.
Note that the seemingly useless statement Local string &temp = MsgGet(0, 0, ""); is necessary, otherwise the last line of the message is repeated, but without parameter substitutions:
This clears away the unbound explain-text returned from MsgGet and MsgGetExplainText.
If you wanted to throw an Exception instead, the same principles apply. You need to use the workaround. You can also strip the stack trace from the message if you don't want that displaying to end-users:
Local string &errorMessage = MsgGet(20007, 6, "MESSAGE NOT FOUND: Too many hours. Decrease percentage.", &emplid, Round(&percentInMonth * 100, 0), Round(&workPercentCap * 100, 0), &neededPercent, 1);
&errorMessage = &errorMessage | Char(10) | Char(10);
&errorMessage = &errorMessage | MsgGetExplainText(20007, 6, "MESSAGE NOT FOUND: Too many hours. Decrease percentage.", &emplid, Round(&percentInMonth * 100, 0), Round(&workPercentCap * 100, 0), &neededPercent, 1);
Local string &temp = MsgGet(0, 0, "");
rem "Remove" the Stack-Trace;
try
throw CreateException(0, 0, &errorMessage);
catch Exception &e
Error &e.ToString( False); rem False means no stack trace;
end-try;
In addition, it is possible to extend the Exception class and to substitute bind values in that way. It is a little more complicated, however. Here is an example:
class CustomException extends Exception
method CustomException(&messageSet As integer, &messageNum As integer, &message As string, &substitutions As array of string);
end-class;
method CustomException
/+ &messageSet as Integer, +/
/+ &messageNum as Integer, +/
/+ &message as String, +/
/+ &substitutions as Array of String +/
%Super = CreateException(&messageSet, &messageNum, &message);
Local integer &i;
For &i = 1 To &substitutions.Len
%Super.SetSubstitution(&i, &substitutions.Get(&i));
End-For;
rem Setting Context to something else replaces the stack trace with whatever text set it to;
%Super.Context = "";
%Super.DefaultText = &message;
end-method;
To make this work, you can pass in an array of bind variables that you want to substitute into the message and then loop through the array and call SetSubstitution() for each variable.
Also, as noted above, if you want the Exception to "throw" like an error message (that is, display the error message without a stack trace), you can erase the Context of the CustomException:
%Super.Context = "";

This works for me -
&string = MsgGetExplainText(25070, 144, "Error Message", &BINDVARIABLE);
&string1 = MsgGetExplainText(99999, 1, """");
Error (&string);

Related

OptaPlanner: How to read game armor data from JSON and find optimal set of armor based on weight + stats?

Elden Ring is a hit game that has some interesting theorycrafting behind it.
There are hundreds of armor pieces, weapons, and spells. Finding optimal combinations of them based on player & item stats is an interesting practical problem.
I've always wanted to learn how to use Constraint Solvers and it seems a good usecase exists!
Goal:
Given a list of all armor in the game in JSON format
Find the set of armor (head, chest, legs, arms) that has:
The highest POISE and PHYSICAL_DEFENSE
For the lowest WEIGHT
Here is the repo:
https://github.com/GavinRay97/elden-ring-optimizer-optaplanner
My attempt so far:
Put the armor data here
Created data class that matches JSON
Created #PlanningEntity class for a combination of armor
Created #PlanningSolution class (not sure if this is correct)
Tried to write Solver, doesn't work
Update
I managed to solve it after advice below
The trick was to change to use PlanningEntityProperty:
#PlanningSolution
public class ArmorSetComboPlanningSolution {
public List<ArmorPiece> armorPieces;
public Map<Integer, List<ArmorPiece>> armorByType;
#ValueRangeProvider(id = "headRange")
#ProblemFactCollectionProperty
public List<ArmorPiece> headList;
#ValueRangeProvider(id = "chestRange")
#ProblemFactCollectionProperty
public List<ArmorPiece> chestList;
#ValueRangeProvider(id = "armsRange")
#ProblemFactCollectionProperty
public List<ArmorPiece> armsList;
#ValueRangeProvider(id = "legsRange")
#ProblemFactCollectionProperty
public List<ArmorPiece> legsList;
#PlanningEntityProperty
public ArmorSet armorSet;
#PlanningScore(bendableHardLevelsSize = 1, bendableSoftLevelsSize = 5)
BendableLongScore score;
ArmorSetComboPlanningSolution() {
}
ArmorSetComboPlanningSolution(List<ArmorPiece> armorPieces) {
this.armorPieces = armorPieces;
this.armorByType = armorPieces.stream().collect(groupingBy(ArmorPiece::armorCategoryID));
this.headList = armorByType.get(0);
this.chestList = armorByType.get(1);
this.armsList = armorByType.get(2);
this.legsList = armorByType.get(3);
// Need to initialize a starting value
this.armorSet = new ArmorSet(0L, this.headList.get(0), this.chestList.get(0), this.armsList.get(0), this.legsList.get(0));
}
}
Then the scorer:
public class ArmorSetEasyOptimizer implements EasyScoreCalculator<ArmorSetComboPlanningSolution, BendableLongScore> {
private final int TARGE_POISE = 61;
private final double MAX_WEIGHT = 60.64;
public ArmorSetEasyOptimizer() {
}
#Override
public BendableLongScore calculateScore(ArmorSetComboPlanningSolution solution) {
long hardScore = 0L;
ArmorSet armorSet = solution.armorSet;
if (armorSet.getTotalPoise() < TARGE_POISE) {
hardScore--;
}
if (armorSet.getTotalWeight() > MAX_WEIGHT) {
hardScore--;
}
long poiseRatio = (long) (armorSet.getTotalPoise() / (double) armorSet.getTotalWeight() * 100);
long physicalDefenseScaled = (long) (armorSet.getTotalPhysicalDefense() * 100);
long physicalDefenseToWeightRatio = (long) (physicalDefenseScaled / armorSet.getTotalWeight());
long magicDefenseScaled = (long) (armorSet.getTotalMagicDefense() * 100);
long magicDefenseToWeightRatio = (long) (magicDefenseScaled / armorSet.getTotalWeight());
return BendableLongScore.of(
new long[]{
hardScore
},
new long[]{
poiseRatio,
physicalDefenseScaled,
physicalDefenseToWeightRatio,
magicDefenseScaled,
magicDefenseToWeightRatio
}
);
}
}
Results
19:02:12.707 [main] INFO org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase - Local Search phase (1) ended: time spent (10000), best score ([0]hard/[179/3540/97/2750/75]soft), score calculation speed (987500/sec), step total (4046).
19:02:12.709 [main] INFO org.optaplanner.core.impl.solver.DefaultSolver - Solving ended: time spent (10000), best score ([0]hard/[179/3540/97/2750/75]soft), score calculation speed (985624/sec), phase total (2), environment mode (REPRODUCIBLE), move thread count (NONE).
[0]hard/[179/3540/97/2750/75]soft
ArmorSet (Weight: 36.3, Poise: 65, Physical: 35.4, Phys/Weight: 0.97, Magic: 27.5, Magic/Weight: 0.75 ) [
head: Radahn Soldier Helm (Weight: 4.0, Poise: 5),
chest: Veteran's Armor (Weight: 18.9, Poise: 37),
arms: Godskin Noble Bracelets (Weight: 1.7, Poise: 1),
legs: Veteran's Greaves (Weight: 11.7, Poise: 22)
]
This is a bit of a strange problem, because you only need one planning entity instance. There only ever needs to be one ArmorSet object - and the solver will be assigning different armor pieces as it comes closer and closer to an optimal combination.
Therefore your easy score calculator doesn't ever need to do any looping. It simply takes the single ArmorSet's weight and poise and creates a score out of it.
However, even though I think that this use case may be useful as a learning path towards constraint solvers, some sort of a brute force algorithm could work as well - your data set isn't too large. More importantly, with an exhaustive algorithm such as brute force, you're eventually guaranteed to reach the optimal solution.
(That said, if you want to enhance the problem with matching these armor sets to particular character traits, then it perhaps may be complex enough for brute force to become inadequate.)
On a personal note, I attempted Elden Ring and found it too hardcore for me. :-) I prefer games that guide you a bit more.

coercing a paramter into an integer in Body Mapping Template, AWS API Gateway

I've been using a bit of arithmetic in the Body Mapping Template in Integration Request:
#set($now = $context.requestTimeEpoch/1000)
#set($soon = $now + 600)
{
"TableName": "events",
.... [ here get events between $now and $soon]
}
Recently I came to need to pass an offset through a parameter:
#set($now = $context.requestTimeEpoch/1000)
#set($soon = $now + $input.params('offset'))
{
"TableName": "events",
.... [ here get events between $now and $soon] ....
}
It turns out that if $now is 1518939082, with query paramter ?offset=600 $soon will be 1518939082600 - a concatenation. I have tried various ways to force the parameter being recognised as an integer, including:
#set($offset = $Integer.parseInt($input.params('offset')))
#set($offset = 0 + $input.params('offset'))
#set($offset = 1 * $input.params('offset'))
None of them works. I inserted #set($offset = 0) before each test so I can tell "nothing happens" from "a nothingness is returned".
In the first case, $offset prints an empty string, not 0. (This happens to $Integer.parseInt("1") too.)
In the second case, $offset prints a concatenation of "0" and the string value of "offset".
In the third case, $offset prints a 0, as if the entire line doesn't exist.
None of them successfully transformed the parameter to an integer.
Is there a way to use that parameter as an integer?

Why does this type conversion of NSString to NSNumber returns invalid value of -1?

This is the line of code in question:
bks.quantity = [NSNumber numberWithInteger: [[arrayOfSplitStrings[i] objectAtIndex:[[sd.dictionaryOfUserIndexes objectForKey: #"29"] intValue]] intValue]-1];
sd.dictionaryOfUserIndexes objectForKey: #"29" contains a string (representing a quantity) that has to be converted to NSNumber. When the statement is executed with a valid string quantity (0-10) it always returns -1, when it is supposed to return the NSNumber for the value.
What am I doing wrong?
This is not a "straight forward" answer (since the solution is just a silly one), it's more a suggestion on work methods, that's why I post an answer.
It's not always good to put it various lines in a single line.
Especially when in your case you encounter an issue. It's better to split each command, one by one, and to debug, check the value of each ones.
In your case:
bks.quantity = [NSNumber numberWithInteger: [[arrayOfSplitStrings[i] objectAtIndex:[[sd.dictionaryOfUserIndexes objectForKey: #"29"] intValue]] intValue]-1];
==>
NSInteger userOfIndexes = [[sd.dictionaryOfUserIndexes objectForKey: #"29"] intValue];
NSLog(#"userOfIndexes: %d", userOfIndexes);
NSInteger n = [arrayOfSplitStrings[i] objectAtIndex:userOfIndexes] intValue];
NSLog(#"n: %d", n);
bks.quantity = [NSNumberWithInteger:n-1];
I added NSLog(), but the values could be check with breakpoints and debugger. I could have also add a check on arrayOfSplitStrings with
NSArray *splitStrings = arrayOfSplitString[i];
NSLog(#"splitStrings: %#", splitStrings);
and replace n with:
NSInteger n = [splitStrings objectAtIndex:userOfIndexes] intValue];
That way, you would have check that apparently (according to your comment), your issue was were to put the "-1.
NSInteger n = [[arrayOfSplitStrings[i] objectAtIndex: userIndex-1] intValue];

GameMaker:Studio switch statement does not execute in http async event

Today I've had majour problems with GameMakers switch statement being executed. When the HTTP ASYNC EVENT is executed, show_message(answer) is executed, but the switch statement under it does not.
I think this is a compiler error, because it seems that the string_length of the "answer variable" is 1, and using string_digits to make sure, that it escapes all the possible blankspaces or unnecessary characters that could appear when retrieving http callback.
Only possible returning values of variable answer are: 0, 1, 2, 3, 4.
So here is the code of HTTP ASYNC EVENT:
if(ds_map_find_value(async_load, "id") == request_auth) {
if(ds_map_find_value(async_load, "status") == 0) {
callback = ds_map_find_value(async_load, "result");
var answer = string_digits(callback);
show_message(answer);
switch(answer) {
case USERNAME_EXISTS:
show_message("username already exists");
break;
case ACCOUNT_CREATED:
show_message("Your account has been successfully created!");
break;
case LOGGED_IN:
buffer_seek(global.buffer, buffer_seek_start, 0);
buffer_write(global.buffer, buffer_u8, 0);
buffer_write(global.buffer, buffer_string, "username");
Send();
break;
case INCORRECT_PASSWORD:
show_message("your password is incorrect");
break;
case INCORRECT_USERNAME:
show_message("your username is invalid");
break;
}
}
else {
callback = noone;
}
}
The string_digits returns a string, not a numeric value. So for example, if your string says "1", the returned value will be 49, which is the ASCII code for "1".
I believe that your macros are assigned to numbers (1, 2, 3 ...) and not to their ASCII code ("1", "2", "3" ... = 49, 50, 51 ...). This could explain why the cases are not triggered.
If you are sure that the switch is read, try putting a "default" at the end of the cases. It will be triggered if the cases were not. You could use it to display the value of "answer", for example, and verify that it is what you want.

Select value from Object in ComboBox

I have a Java Object which I want to edit using this code. The value which I want to get from this obj.getSizeBetweenMessages() is 10:
ObservableList<Integer> zoptionsm = FXCollections.observableArrayList(
obj.getSizeBetweenMessages(), 90);
ComboBox<Integer> zcombom = new ComboBox<>(zoptionsm);
zcombom.getSelectionModel().select(obj.getSizeBetweenMessages());
zcombom.setEditable(true);
gpm.add(zcombom, 1, 1);
For some reason the ComboBox is empty and the value into the Object is not displayed. Could you give me some idea where I'm wrong?
If your getSizeBetweenMessages() method is returning an int, (not an Integer), you're relying on autoboxing both when you pass the result to FXCollections.observableArrayList(...) and zcombom.getSelectionModel().select(...). In the first case, this works, because there is no appropriate observableArrayList(...) method taking an int, but in the second case there is a select(...) method accepting an int, and that is the one that gets invoked. So if getSizeBetweenMessages() returns 10, you are trying to select the item at position 10 (not the item 10 itself), and since that's out of bounds nothing gets selected.
The fix is to create an Integer object by hand:
Integer sizeBetweenMessages = new Integer(obj.getSizeBetweenMessages());
ObservableList<Integer> zoptionsm = FXCollections.observableArrayList(
sizeBetweenMessages, 90);
ComboBox<Integer> zcombom = new ComboBox<>(zoptionsm);
zcombom.getSelectionModel().select(sizeBetweenMessages);
zcombom.setEditable(true);
gpm.add(zcombom, 1, 1);

Resources