Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Khris

#61
Here's code that switches the cursor mode as opposed to the graphic:

Code: ags
bool wasOverActiveArea;
void HandleCursor() {
  if (Mouse.Mode == eModeUseinv) return; // do nothing
  bool isOverActiveArea = GetLocationType(mouse.x, mouse.y) != eLocationNothing; // compare to enum!
  if (isOverActiveArea && !wasOverActiveArea) {
    // mouse just moved over active area
    Mouse.Mode = eModeInteract;
  else if (!isOverActiveArea && wasOverActiveArea) {
    // mouse just left active area
    Mouse.Mode = eModeWalkto;
  }
}

Switching to the woman should call
Code: ags
  Mouse.ChangeModeGraphic(eModeInteract, SPRITE_SLOT_OF_RED_HAND);

and switching to the man should call
Code: ags
  Mouse.ChangeModeGraphic(eModeInteract, SPRITE_SLOT_OF_BLUE_HAND);

To recap: moving the mouse only switches cursor modes between walking and interacting, switching the player character changes the sprite of the eModeInteract cursor.

(As an aside, I find the Mouse.UseModeGraphic() command quite problematic. It essentially fools the player, and is used as a quick fix instead of properly coding mouse behavior. It shouldn't be used.)
#62
The main downloads here https://www.adventuregamestudio.co.uk/#create seem to have an issue.

When I try the zip I end up with a temporary file whose contents starts with PK and that's a few megabytes in size. But the download box in Firefox says "Failed" and the download doesn't finish.
#63
These transitions are hardcoded and cannot be changed. You would have to write a faster one yourself. You can do this using a screen-sized GUI and a screen-sized DynamicSprite set as its BackgroundGraphic.

(Also, ChatGPT is not suitable to help with AGS programming.)
#64
Couldn't one simply play the flute sound via voice speech though? Like
  player.Say("&123");
#65
Isn't there a setting where you can specify how much the background music's volume is lowered while AudioClips are played? Or does that only apply to voice speech?
I looked through the manual but couldn't find it.
#66
Not sure what the best solution is; maybe something like this in the templates, which people can now override however they want:

Code: ags
function on_mouse_gui_click(MouseButton button)
{
  if (button == eMouseLeft)
  {
    GUI.ProcessClick(mouse.x, mouse.y, Mouse.Mode); 
  }
}
#67
Just a tiny cosmetic issue:

The favicon has a black background, as opposed to a transparent one:


Is this deliberate?
#68
When you click on a GUI, on_event is called with eEventGUIMouseDown as the first parameter, so you should probably wrap your code inside that (although it doesn't make that much of a difference).

You should also check which button if any is under the mouse (using GUIControl.GetAtScreenXY).

I tried to implement a generic solution that stores all .PushedGraphic values in an array, then sets them to -1. My code also calls on_mouse_click(eMouseRight), i.e. automatically redirects the click to be handled as a non-GUI one.

Everything works so far, the only (major) issue is that there seems to be no way to prevent the button's OnClick from running other than to manually exit it in each handler separately based on a dont_handle variable. Possible to do but kind of annoying.
#69
Open VerbGui.asc, scroll down to line 1980 and use this:

Code: ags
String GetRandomLookResponse() {
    int index = Random(7);
    if (index == 0) return "It looks... like a thing.";
    if (index == 1) return "Yup, that's definitely what that is.";
    if (index == 2) return "I see it, but what am I supposed to do with it?";
    if (index == 3) return "Looking at it harder isn't going to help.";
    if (index == 4) return "Cool. Now what?";
    if (index == 5) return "Great. I've observed it. Achievement unlocked.";
    return "It's there, I'm here. What a time to be alive.";
}

void ShuffleUnhandled() {
  verbsData.unhandled_strings[eVerbGuiUnhandledLook] = GetRandomLookResponse();
  // verbsData.unhandled_strings[eVerbGuiUnhandledUse] = GetRandomUseResponse();
  // ...
}
 
static void Verbs::Unhandled(int door_script) 
{
  ShuffleUnhandled();
  InventoryItem*ii = InventoryItem.GetAtScreenXY(mouse.x, mouse.y);
  int type=0;
  if (verbsData.location_type == eLocationHotspot)   type = 1;

(At the bottom is the top of the existing Verbs::Unhandled function, with the added ShuffleUnhandled() line.)
#70
Not sure I understand the question; you're free to use whatever you like (including my code) :)

I get that posting code like mine is kind of useless unless it includes an in-depth explanation; I just wanted to show what is possible in general with regard to efficiency.
#71
Please always (always) post the code you're using, not just the error message. A missing parameter is one of the easiest problems to solve by far, so why drag this out? Just post the command you're using in the post you're writing. Like when you describe the error, why not also put the line into the post that caused the error? Because if we can see the line, we can fix it in the reply, instead of having to ask for you to also post the command you used. Less wasted time for everybody who reads or posts here, if you simply copy and paste the command you've used in the post you're writing. It takes a few seconds but saves so much precious time.
#73
The general approach is to first think about the best way to store the state of the puzzle. Here it's an array of size 9 where each element stores either 0 (the hole) or a number from 1 to 8 (the tiles).

The next thing you need is a function that displays this state on the screen. You use a loop to iterate over the array, calculate the coordinates based on the array index, then position / move the objects accordingly.
(This also requires some pre-planning, i.e. ideally having consecutive sprite slots and object IDs for the tiles).

The final thing you need is click handling. AGS supports using a single function to handle clicking every one of the eight objects. You can then use either the mouse coordinates and some math or the object that is passed to the handler function to figure out which tile was clicked.
If the click is valid (i.e. a tile next to the hole was clicked) you 1) update the game state 2) re-run the function that updates the screen.

Now let's do a "short" dive into the required array index math. The array will look something like this:
 [3, 6, 2, 0, 4, 1, 5, 7, 8]

I.e. the first row contains tiles #3, #6 and #2, the second row contains the hole, then tiles #4 and #1, etc.

When the player clicks an object, we can use the top left coordinates of the board and the tile size to calculate which one way clicked.
Code: ags
  int x = (mouse.x - top_left_coordinate) / tile_size; // 0, 1 or 2
If the coordinates are correct, x is now guaranteed to be 0, 1 or 2 because we clicked on object so we are inside the proper coordinate range by definition.
We can do the same for y and will also get a value from 0, 1 or 2.
Using y * 3 + x we can turn this into the array index (0-8).

Figuring out whether the hole is a neighbor is a bit more complicated. In general, checking the four cardinal directions means we're checking coordinates x,y-1 and x+1,y and x-1,y and finally x,y+1
Since you asked for the most efficient way, here it is:
Code: ags
  for (int di = 1; di <= 7; di += 2) {
    int dx = di % 3 - 1;
    int dy = di / 3 - 1;
    // neighboring coordinates xn/yn
    int xn = x + dx;
    int yn = y + dy;
    if (xn >= 0 && xn <=2 && yn >= 0 && yn <=2) { // neighbor is part of the board
      int ni = yn * 3 + xn; // calculate neighbor's array index
      if (board[ni] == 0) {  // if neighbor is the hole
        // swap values of board[clicked] and board[ni] i.e. clicked tile and hole
        // update the screen
        break; // exit the loop so we don't accidentally reverse the swap in a later loop
      }
    }
  }
This loop runs four times with di being 1, 3, 5 and 7. dx and dy will be 0/-1, -1/0, 1/0 and 0/1 respectively.
Thus xn/yn will be all four direct neighbors of x/y in terms of 2D board coordinates.
#74
This is not a bug; importing an image replaces all existing walkbehinds because AGS assumes that the image contains all walkbehinds. You're supposed to use the first X palette slots to draw all of them, as opposed to importing them individually. (Walkbehinds cannot overlap, and are stored as a single image internally.)

The only way to keep existing ones is to temporarily replace the room background with a single-color image, take a screenshot at 100% zoom level and combine that with the new map in a paint program.

You can also open a copy of the game in AGS4; this will create a png of the walkbehinds in the game/Rooms/X folder.
#75
You can just use Notepad.exe to open the game file; it's human readable XML (just text).

I recommend making a copy of the game files (minus the resources) and using 3.6.1 to work on it. You'll barely have to make any changes if at all but a bunch of bugs will be fixed.
#76
The Character.FaceDirection method was added to the built-in API at some point after 3.2.0 came out.
Since the custom one in your game uses different enum values, you cannot simply remove the custom function.

A quick fix is to rename the custom one to FaceDirectionOld or something. Just let the script editor replace every occurrence of "FaceDirection" with "FaceDirectionOld" in guiscript's header and main script and all room scripts.

To do a clean refactor, you need to remove the function and header line from guiscript.asc / guiscript.ash, then replace every "eDir_Up" with "eDirectionUp" etc.
#77
You can use the Recruitment board to find somebody.
#78
A first step would be to replace myCounter1, myCounter2, etc. with properly named arrays.
Using arrays is very basic but also very useful.

Declare one at the top of the script:
  int temp[3]; // three is the size, i.e. the number of variables you'll get

This creates three variables: temp[0], temp[1] and temp[2]. You can use these exactly like you would temp1, temp2 and temp3 with the added benefit that you can use a variable as the index:
  int a = 1;
  temp[a] = 4;

You can even use an expression:
  temp[(4 + 2) / 3]
for instance is perfectly fine and will be the equivalent of temp[2].
(These examples are pretty nonsensical, they're just supposed to show what is possible)

The next useful thing is custom functions; you're already using them. It essentially means turning a bunch of lines into a single command, with the additional benefit of being able to customize each call using the function's parameters.

Writing code for Myst-style puzzles is way easier to do and debug if you learn a few basic programming concepts first.
Like Snarky said, it's not just about implementing this specific puzzle.
#79
Made a demo game:

https://drive.google.com/file/d/1Sypp0xs5XjlZUQleTxxjGfJbeQxGkBvB/view?usp=sharing

I implemented the three buttons; they switch hatches and randomly set the temperature and level.

This uses three sprites only, a few more are needed to polish the optics. The colored squares are rectangles drawn to the background, then the panel is drawn on top.

Here's the room script:
Spoiler
Code: ags
// room script file

// for each line, store active column, temp and level
int hatch[3];
int temp[3];
int level[3];

// 6 colors for 6 temperatures
int col[6];

void DrawSquare(DrawingSurface* ds, int line) {
  ds.DrawingColor = col[temp[line]];
  int x = hatch[line] * 220 + 370;
  int y = line * 140 + 180;
  ds.DrawRectangle(x, y + 40 - level[line] * 5, x + 60, y + 60);
}

void UpdateScreen() {
  DrawingSurface* ds = Room.GetDrawingSurfaceForBackground();
  ds.Clear(0);
  // draw squares on background
  for (int line = 0; line < 3; line++) {
    DrawSquare(ds, line);
  }
  // draw panel on top
  ds.DrawImage(286, 135, 3);
  // draw green lights on panel
  for (int line = 0; line < 3; line++) {
    for (int column = 0; column < 3; column++) {
      int slot = 2; // off
      if (hatch[line] == column) slot = 1; // on
      ds.DrawImage(column * 215 + 475, line * 142 + 175, slot);
    }
  }
  ds.Release();
}

function room_Load()
{  
  // grey
  col[0] = Game.GetColorFromRGB(128, 128, 128);
  // light orange
  col[1] = Game.GetColorFromRGB(255, 220,  80);
  // orange
  col[2] = Game.GetColorFromRGB(255, 160,   0);
  // dark orange 
  col[3] = Game.GetColorFromRGB(210, 135,   0);
  // light red
  col[4] = Game.GetColorFromRGB(255, 100, 100);
  // dark red
  col[5] = Game.GetColorFromRGB(170,   0,   0);
  
  UpdateScreen();
}

void HandleLine(int line) {
  hatch[line] = (hatch[line] + 1) % 3; // switch column
  // random values for now
  temp[line] = Random(5);
  level[line] = Random(5);
  UpdateScreen();  
}

function hButton_AnyClick(Hotspot *theHotspot, CursorMode mode)
{
  // hotspots 1-3 => lines 0-2
  HandleLine(theHotspot.ID - 1);
}
[close]
#80
OP was copy-pasting the two dozen lines then replacing 11 with 12 but forgot to also rename the variables is my assessment.
So the second block is very much supposed to run in all cases and supposed to handle the second lava pipe segment (object[12]) but doesn't because the segment #1 / object[11] code always prematurely exits the function.

If I'm wrong it doesn't really matter anyway because this entire exchange is moot. This approach is fundamentally flawed and using it should not be encouraged even if certain minor aspects seem like a good idea. I hate to belabor this point but writing code like this needs not to happen, ever.

As an aside, I do not adhere to the "only one return statement" principle because it is bound to create a bunch of nesting but at the same time one obviously shouldn't use returns indiscriminately.


Using a view to connect the two variables and the according sprite is a great idea!
SMF spam blocked by CleanTalk