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 - deadsuperhero

#1
This project started off as many do: someone in a chatroom asking a question about how to do something. Someone asked about how to make text scroll in AGS for a parser game, and something about the question got me thinking about how I might do it.



Four days later, I've managed to hack together a working prototype of the module, dusted it off, and named it Promptly.
Promptly can do a couple of unique things, and it's all based on hacks on top of hacks. This text parser can:

  • Print out and parse dialog options, and then run them.
  • Check inventory items, and interact with them.
  • Provides context fallbacks, so that you don't get gummed up switching from, say, working with items, to talking to people.
  • Lets you show and hide images for things like dialogue and inspecting items
  • Provides scrollback, but lets you take it away in moments that the player needs to focus on just one thing.

There are still a few things I'd like to do with this, such as theme customization / palette swap support for 8-bit projects. Some of the code is a bit messy and requires clean-up, but I'm really happy with what I've managed to accomplish so far. Big shout out to CrimsonWizard for helping me figure out how to split up strings from the parser!

> SOURCE CODE
> DOCUMENTATION
#2
Hey, just in case you were curious about ways to implement this thing, I'm building an inventory-based dialogue system for one of my games. My project is fully open source; you're welcome to take a gander and see how stuff works:

Kind of in a transitional phase right now from an older system, so it currently only works on one character (blonde guy at a party) and most of it needs to be restructured, but it might give you some ideas!

https://codeberg.org/UnicornSausage/deep-cuts/issues/5

The basic concept I have is that certain items are assigned to an off-screen character whose only purpose is to store them.

A speech GUI draws some buttons and counts how many items that character has, then does some magic by filling single-item inventory windows above each button. Those windows ignore clicks, the button underneath checks what item is supposed to be in that slot, then it runs a dialog action on characters when clicked.
#3
General Discussion / Re: Development hell
Fri 24/03/2023 05:50:17
Pretty much all of my game projects are in development hell, but the situation and my state of mind are actually improving.

My Achilles Heel in the past was mainly due to scope creep - a simple idea took on bigger and bigger elements. Early in a game's life, it feels as though your creation can go in any direction: you can design a completely bespoke interface and way of doing things, introduce novel game mechanics, and attempt to do rich world-building.

In my case, this can become a nightmare. An RPG can become an elaborate simulation where the NPCs are designed to follow paths resembling everyday life. A conversation game can expand into an entire cityscape with a transit system and puzzles with a dozen different solutions and outcomes.

This can seem fanciful, and to some degree, can allow you to develop amazing things. But, it can impede development of the actual game, where very little functions outside of the crazy part you built.

My other problem is that I'm a one-man team. I don't like managing other people, can't afford to commission designers, and generally don't feel confident about recruiting a bunch of people or building some kind of community. So, this means that I do all of the programming, character design, background art, animation, music composition, and writing. In the service of making the products of my imagination a reality, it's worth it...but it's exhausting.

What ultimately has helped me is to slow down, take a step back, and take a long break. Game development for me is a hobby, where I explore what's possible for me to create. Taking the whole thing less seriously, and also simplifying the scope of what I want to accomplish, very much help move my closer to the goal.
#4
Quote from: eminigalaxy on Fri 17/03/2023 00:15:30Hey sorry to bother but saw this and was wondering if you ever did end up making a template for the quest UI?  :-D

Not yet, but I could probably knock something together this weekend.
#5
Quote from: Khris on Fri 17/03/2023 08:54:44emini: try sending him a PM maybe :)

Re the code, this:
Code: ags
// Create a Quest
createQuest("Get Ready for Work", "You need to get ready to go to the office. Don't be late!");

// Assign three Tasks to Quest 1
createTask("Get Ready for Work", "Get Dressed");
createTask("Get Ready for Work", "Eat Breakfast");
createTask("Get Ready for Work", "Drink Coffee");
seems pretty redundant, I'd store the quest name in a temporary variable and use that to assign the tasks.

Yeah, that's pretty good feedback. Right now, Tasks and Quests are created by two separate functions for two separate structs, and related to one another by the Quest name.

I guess you could wrap them in another function and pass through Tasks and Quests at the same time, but you'd still have to use the method I illustrated to assign subsequent Tasks to an existing Quest later on.
#6
Quote from: CaptainD on Sat 24/12/2022 10:20:35Aw, you don't count me as a cool person you found on there!  :shocked:  :grin:

Anyway, Here I am although in truth, I'm unlikely to spend a lot of time there unless Twitter does actually implode.

Oops! I thought I had added you!  :shocked:
List updated.  :grin:
#7
With everything going on with Twitter these days, there's been an increased interest in alternative spaces for people to congregate. One of these spaces is Mastodon, which in some aspects is very similar to Twitter due to being designed around microblogging. One fundamental difference, though, is that the network is not just on one website, but many sites that can all talk to each other.

Over the past few months, Mastodon has increased by roughly 2.5 million people, across a staggering 9,000+ servers (called "instances"). With all that being said, Mastodon is part of an even bigger network, though: the fediverse. It's kind of like a modern incarnation of Usenet, but with the conveniences of web interfaces and mobile apps. There are replacements for many different kinds of services: YouTube, Instagram, GoodReads, Reddit, MeetUp, GrooveShark, and Apple Podcasts. WordPress blogs and Drupal sites can integrate with it, and Tumblr and Flickr are mulling over native integrations to the network, too. All of these things are capable of talking to each other through a common protocol: a Mastodon user can follow someone from Pixelfed and see their photos in the stream, or do the same with a PeerTube user and see their videos. The Mastodon user's replies show up as native comments at the post's point of origin.

Anyway, with that explainer out of the way, I wanted to ask: are you on the network somewhere? I've noticed a handful of notable current and former AGS'ers on there so far, many of whom are on GameDev Place. Here are a couple of cool people I've found from the community:


Along with a couple of famous gamedevs:

Those are all of the ones I've managed to find so far. There are tons of indie game devs outside of the AGS Community, and in fact, lots of people from all walks of life on the network. Anyway, if you're interested in joining, or already on here, feel free to drop your link in th replies, and I'll update the list!
#8
Quote from: Indra Anagram on Tue 09/08/2022 14:04:32
People, does anyone have all this plugin versions and tutorials? The links in the original post lead to Google Drive page that doesn't allow me to download anything, saying I've got no access  :~(

Hey, I don't think this is actively developed anymore. Your best bet might be to instead use this plugin developed by Dualnames.
#9
Yay, I figured it out! :cheesy:
Thanks for setting me on the right path, DualNames and Snarky. <3

It turns out that I was really overthinking this a lot.

Code: ags


function deadzoneCheck() {
  // Right Stick -- Horizontal
  if (axis2 <= 2000 && axis2 >= -2000) {
    dz_horiz_left=true;
  }
  else if (axis2 > 2000 || axis2 < -2000) {
    dz_horiz_left=false;
    deadZone = "False";
  }
  
  // Right Stick -- Vertical
  if (axis3 <= 2000 && axis3 >= -2000) {
    dz_vert_left=true;
    deadZone = "True";
  }
  else if (axis3 > 2000 || axis3 < -2000) {
    dz_vert_left=false;
    deadZone = "False";
  }  
}

function trackAxis() {
  String inputName = gamepad.GetName();
  if (gamepad.Plugged() == true) {
    axis0 = gamepad.GetAxis(0); // Left Stick
    axis1 = gamepad.GetAxis(1); // Left Stick
    axis2 = gamepad.GetAxis(2); // Right Stick
    axis3 = gamepad.GetAxis(3); // Right Stick
  }
    if (inputName == "PS5 Controller") {
    axisCo2 = gamepad.GetAxis(2) / 2000; // Rate of Horizontal Motion
    axisCo3 = gamepad.GetAxis(3) / 2000; // Rate of Vertical Motion
    deadzoneCheck();
    
    
    // Stick Input
    if (dz_horiz_left == false || dz_vert_left == false){ // RIGHT STICK
      Mouse.SetPosition(mouse.x + axisCo2, mouse.y + axisCo3);
    }
   }
}


Checking for deadZone 100% fixed my problem, thanks for taking the time to educate me on best practices!
#10
Oh, man! This is exactly what I need right now for my projects. Very exciting to see this, would love to get my games working on SteamDeck!
#11
Quote from: Snarky on Fri 29/04/2022 09:44:07
Couldn't you make the stick control the movement of the cursor rather than its absolute position directly?

I think you're on the right track, this does seem to be an improvement of sorts...
One thing that I've noticed with this plugin: the axis values never seem to be zero with my controller. I'm not sure if this is a bug in the plugin or my drivers, or just how controllers are in general, but my PS5 DualSense controller always registers as a non-zero number, even when the sticks aren't moving.

I'm going to keep trying to debug this on my end, it could just be that something in my setup is giving me an unnecessary headache. I just can't figure out how to represent a "joystick at rest" setting so that the game engine knows not to move the cursor any further. Right now, it just kind of slowly drifts all over the place.
#12
Hey, just digging this up again. I took a long break from doing AGS stuff, and kind of wanted to get back into making my games work with a game controller.
One area that I'm kind of stuck on involves moving the mouse with a joystick in a way that feels good.

In my current setup, the right stick on my gamepad moves the cursor around.

Code: ags

function trackAxis() {
  if (gamepad.Plugged() == true) {
    int axis0 = gamepad.GetAxis(0); // Left Stick Vertical
    int axis1 = gamepad.GetAxis(1); // Left Stick Horizontal
    int axis2 = gamepad.GetAxis(2); // Right Stick Vertical
    int axis3 = gamepad.GetAxis(3); // Right Stick Horizontal
  }
}

function checkController() {
trackAxis();
String inputName = gamepad.GetName();

  if (inputName == "PS5 Controller") {
    int axis2 = gamepad.GetAxis(2); // Right Stick Vertical
    int axis3 = gamepad.GetAxis(3); // Right Stick Horizontal
    int axisCo2 = gamepad.GetAxis(2) / 80; // 1/4th of the Room Height
    int axisCo3 = gamepad.GetAxis(3) / 50; // 1/4th of the Room Width
    
    if (axis2 < -200 && !LEFT){ // RIGHT STICK LEFT
      Mouse.SetPosition(axisCo2, axisCo3);
      HitNoDirection();
      LEFT=true;
    }
    else if (axis2 > 200 && !RIGHT){ // RIGHT STICK RIGHT
      Mouse.SetPosition(axisCo2, axisCo3);
      HitNoDirection();
      RIGHT=true;
    }
    else if (axis3 < -200 && !UP){ // RIGHT STICK UP
      Mouse.SetPosition(axisCo2, axisCo3);
      HitNoDirection();
      UP=true;
    }
    else if (axis3 > 200 && !DOWN){ // RIGHT STICK DOWN
      Mouse.SetPosition(axisCo2, axisCo3);
      HitNoDirection();
      DOWN=true;
    }
    else {
      HitNoDirection();
    }
}


For purposes of processing input, I'm calling my code from repeatedly_execute_always, so that the player can interact with the game even during speech and cutscenes. The problem is that the right stick has a Rubber Band effect, where the cursor returns to its position of origin after the stick is released. In other words...you can position the cursor around, but it always returns to a given spot.

I'm not completely sure on how to avoid this. My guess is that, because this is running in repeatedly_execute_always, the engine is constantly polling for mouse position based on joystick input, and updating it even when the axis values are zero?

I'm just trying to figure out how to get the cursor to stay put after I move it with the stick.
#13
Really cute adaptation, good job!
#14
Hey, sorry for the delay - it turns out that I was calling it from repeatedly_execute! That one was 100% my bad, moving over to repeatedly_execute_always totally fixed it.

Looks like I'll have to come up with something clever for working around the Display() issue.
#15
Quote from: Crimson Wizard on Wed 19/01/2022 20:59:26
Quote from: deadsuperhero on Wed 19/01/2022 09:35:41
So...the Wheel menu buttons work fine when left clicking...but! When a message comes up on the screen, this simulated left click is incapable of dismissing them. It also can't seem to skip speech.

Can you clarify, where exactly do you call that code from? What do you refer to as a "message", is this a message from Display() function?

These should be identical in result. What matters is what circumstances you are calling them (e.g. on which event or callback).

Hey, no problem! This is what I'm currently doing for handling clicks normally:

Code: ags


  if (button == eMouseLeft) {
   if (gWheel.Visible == false) {
     Room.ProcessClick(mouse.x, mouse.y, mouse.Mode);
   }
   else {
     gWheel.Visible = false;
     gContext.Visible = false;
   }
  }




For deeper context, here's my click handler in entirety:
Code: ags

function on_mouse_click(MouseButton button)
{
  // called when a mouse button is clicked. button is either LEFT or RIGHT
  if (IsGamePaused())
  {
    // game is paused, so do nothing (i.e. don't process mouse clicks)
    if (gInventory.Visible == true) {
       if (button == eMouseLeftInv) {
              if (mouse.Mode == eModeLookat) {
                examineItem(inventory[game.inv_activated]);
               }
               else if (mouse.Mode == eModeInteract) {
                 inventory[game.inv_activated].RunInteraction(eModeInteract);
               }
               else if (mouse.Mode == eModeUseinv) {
                 inventory[game.inv_activated].RunInteraction(eModeUseinv);
               }               
               else if (mouse.Mode == eModePointer) {
                 player.ActiveInventory = inventory[game.inv_activated];
                 mouse.Mode = eModeUseinv;
                 mouse.ChangeModeHotspot(eModeUseinv, 0, 0);
               }               
      }
    }
     else if (gMemories.Visible == true) {
       if (button == eMouseLeftInv) {
         openJournal(inventory[game.inv_activated]);
       }
     }
    
  }
  else if (button == eMouseLeft)
  {
   if (gWheel.Visible == false) {
     Room.ProcessClick(mouse.x, mouse.y, mouse.Mode);
   }
   else {
     gWheel.Visible = false;
     gContext.Visible = false;
   }
  }
  else if (button == eMouseRight){
    // right-click or mouse wheel down will cycle the mouse cursor mode forwards
    setContext();
    setPointer();
 //   gWheel.SetPosition(mouse.x, mouse.y);
    gWheel.Visible = true;
  }
  else if (button == eMouseMiddle)
  {
    // middle-click makes the character walk to clicked area, regardless of cursor mode
    Room.ProcessClick(mouse.x, mouse.y, eModeWalkto);
  }
}


To clarify - skipping speech, cutscenes, and Display() messages works just fine with both the mouse and the keyboard. But, the gamepad input just seems to ignore it.
#16
Edit: Before reading any of this, note that I am using a Playstation 5 controller, which is less common on a PC (Linux) setup. It's entirely possible that different controllers read axis values differently.

Hey, so I'm currently working on adapting this to my game, but I have a couple of stumbling blocks to figure out:

So...the first one is getting the axis to work properly. In a nutshell, I'm trying to make one of my joysticks move the mouse cursor around. I have an extremely rough implementation right now:

Code: ags

if (gamepad.GetAxis(0) < -200 && !LEFT){ // LEFT STICK LEFT
   Mouse.SetPosition(mouse.x - 1, mouse.y);
   HitNoDirection();
   LEFT=true;
   }
    else if (gamepad.GetAxis(0) > 200 && !RIGHT){ // LEFT STICK RIGHT
      Mouse.SetPosition(mouse.x + 1, mouse.y);
      HitNoDirection();
      RIGHT=true;
    }
    else if (gamepad.GetAxis(1) < -200 && !UP){ // LEFT STICK UP
      Mouse.SetPosition(mouse.x, mouse.y - 1);
      HitNoDirection();
      UP=true;
    }
    else if (gamepad.GetAxis(1) > 200 && !DOWN){ // LEFT STICK DOWN
      Mouse.SetPosition(mouse.x, mouse.y + 1);
      HitNoDirection();
      DOWN=true;
    }
    else {
      HitNoDirection();
}


Unfortunately, the cursor seems to get confused about where it's supposed to go on the screen. Up and left seem to work okay, but the game seems to get confused about down and right.


The second stumbling block is that I'm trying to treat the X button as a Left Click, and the Square button as a Right Click:

Code: ags

    if (gamepad.IsButtonDownOnce(0)) // X Button
      {
        if (gWheel.Visible == true) {
          Wait(1); // Have to have this, otherwise the first lines of dialogue get skipped?
          Mouse.Click(eMouseLeft); // Clicking on buttons
        }
        else if (gInventory.Visible == true) {
          Mouse.Click(eMouseLeftInv); // Interact with Inventory Items
        }
        else {
          Mouse.Click(eMouseLeft); // Normal default Left Click, which is normally just walking to a specific point.
        }
      }


So...the Wheel menu buttons work fine when left clicking...but! When a message comes up on the screen, this simulated left click is incapable of dismissing them. It also can't seem to skip speech.

#17
I wasn't 100% sure whether this fell on the side of the AGS Editor, or the Engine itself - however, I figured that since such a feature would technically be part of a runtime, it might be better to start off here.

Historically, Adventure Game Studio has shipped with support for two types of input: mouse, and keyboard. This makes sense, given that the form factor of most AGS games follow the traditional "point-and-click" form factor that we all know and love.

However, in recent years, it has been possible to port AGS titles to:
  • Android
  • iOS
  • PSP (maybe even Vita?)
Another consideration is that some games have received special ports, such as Unavowed coming to the Nintendo Switch.

My point is: it's technically possible for Adventure Game Studio to support a lot of different form factors, including devices with game controllers as well as tablets that have multitouch.
It's already possible, through the inclusion of DualName's excellent Controller plugin (via SDL), to have an AGS game that supports just about any USB game controller that the host OS can interface with.

The problem is, adapting games to use different types of input is actually pretty hard. The controller plugin works great in principle, but adapting my code to it requires a lot of thoughtful workarounds and trial and error...for example, it currently isn't possible to treat a gamepad button as a left click that can dismiss messages in the game, and trying to make arrow buttons on a pad imitate key presses has its own set of issues.

What would be really amazing, given that AGS is shifting towards being based on SDL, would be for the engine and Script API to consider native support for input types supported by gamepads and touch inputs. Here's a non-exhaustive set of examples of the kind of interactions that we could theoretically support:

Multitouch:
  • Tap
  • Tap and Hold
  • Pinch to zoom
  • Drag and Drop

Gamepad
  • Button Press
  • Button Hold
  • Menu Item Selection
  • Axis tracking
#18
Quote from: Volcan on Mon 17/01/2022 02:31:20
Quote from: eri0o on Sun 16/01/2022 23:37:41
The SDL2.dll has to be 32 bits and placed in the same directory.

I don't get it since SDL2.dll is included with AGSController.dll.

Hey, I had the same problem earlier today. If you check earlier in the thread, it turns out you need to get the 32-bits version of the DLL from the LibSDL site: https://www.libsdl.org/download-2.0.php
#19
Hey, so just wanted to bring this to a close - I ended up solving this today! I've been messing with Structs a lot for a different game, and the solution of associating things together ended up being a lot simpler than I was initially giving credit.

TL;DR - The Quest system I was trying to build failed because it was trying to use some kind of ID as a reference point to look up related tasks, but everything was convoluted and built on some very silly assumptions of how best to generate Struct objects and parse through them.

Here's a breakdown of what my system actually does now:

First - I found it much easier to create Struct objects as a separate function not related at all to the Struct object itself. Instead, just get a count of all Quests and Tasks in the game, and increment that number to determine the ID of the next one.

Code: ags

function createQuest(String questTitle,  String questDesc) {
    // Currently refactoring, might not work
  int i = questCount;
  quest[i].title = questTitle;
  quest[i].desc = questDesc;
  quest[i].complete = false;
  questList.AddItem(quest[i].title);
  questCount++;
}

function createTask(String pq, String taskDesc) {
  int i = taskCount;
  task[i].desc = taskDesc;
  task[i].parentQuest = pq;
  task[i].complete = false;
  taskCount++;
}


What I was trying to do before for Struct object generation was super unwieldy. Here's a before and after comparison:

Before:
Code: ags

// Create a Quest
quest[1].Create("Get Ready for Work", "You need to get ready to go to the office. Don't be late!");

// Assign three Tasks to Quest 1
task[0].Create(1`, "Get Dressed");
task[1].Create(1, "Eat Breakfast");
task[2].Create(1, "Drink Coffee");


After:
Code: ags

// Create a Quest
createQuest("Get Ready for Work", "You need to get ready to go to the office. Don't be late!");

// Assign three Tasks to Quest 1
createTask("Get Ready for Work", "Get Dressed");
createTask("Get Ready for Work", "Eat Breakfast");
createTask("Get Ready for Work", "Drink Coffee");


The great thing now is that, the new way of doing things doesn't care about IDs - they're unimportant. Quest names in this system are unique enough, and are what I'm using to do all the lookup logic! AGS literally doesn't care.


Code: ags

// Refreshing the Quest List, which is called every time the Quest UI is invoked.
function refreshQuests() {
  // Refresh the List of Quests
  if (questList.ItemCount > 0) {
    questList.Clear();
  }
  
  // Add Completed Quests
    for (int i = 0; i < questCount; i++) {
      if (quest[i].complete == false) {
         questList.AddItem(quest[i].title);
      }
  }
  
  // Set the Selected Index
  int comboIndex = questList.SelectedIndex + completedQuests;
  
  // Refresh the tasks for good measure
  lookupTasks(quest[comboIndex].title);
  

// Refreshing the Task List, technically this shows up higher in the script so that the prior function can call it.
function lookupTasks(String questTitle) {
  
    // Refresh the Task List
    if (subTasks.ItemCount > 0) {
      subTasks.Clear();
    }
    
    // List all of the Tasks the game knows about
    for (int i = 0; i < taskCount; i++) {
      if ((task[i].parentQuest == questTitle) && (task[i].complete == false)) {
      subTasks.AddItem(task[i].desc);
    }
  }
}



A few weird things I had to do to cover my butt:

Whenever the Quest UI is invoked, it needs to be told to select the topmost item on the Quest list, in order to pull in the Task items associated with a quest.

Code: ags

function btnQuests_OnClick(GUIControl *control, MouseButton button)
{
  int comboIndex = questList.SelectedIndex + completedQuests;
  if (questList.ItemCount > 0) { // Game crashes if it tries to do all this stuff with no quests in the system, so this loop is an extra bit of protection
    refreshQuests();
    qDesc.Text = quest[comboIndex].desc; // Sets the description of the Current Text
    gQuests.Visible = true;
  }
  else {
    Display("You have no quests.");
  }
}


Whenever a selection on the Quest list gets updated, I have to dynamically change everything:

Code: ags

function questList_OnSelectionChanged(GUIControl *control)
{
 int comboIndex = questList.SelectedIndex + completedQuests;
 String selectedQuest = quest[comboIndex].title;
 String selectedDesc = quest[comboIndex].desc;
  
  // Check the quest details
  lookupQuest(selectedQuest);
  
  //Adjust Label
  qDesc.Text = selectedDesc;
  
  
  // Update Listed Tasks
  lookupTasks(selectedQuest);
  
}


Finally, I had to do one really weird thing to account for Completed quests that no longer show up in the UI:

Code: ags

function completeQuest(String questTitle) {


  // Mark all matching tasks as Complete
  for (int i = 0; i < questCount; i++) {
    if ((quest[i].title == questTitle) && (quest[i].complete == false)) {
       quest[i].complete = true;
       completedQuests++; // Increment this so that other quests still properly align with the index
    }
  }

// Mark all matching tasks as Complete
  for (int i = 0; i < taskCount; i++) {
    if ((task[i].parentQuest == questTitle) && (task[i].complete == false)) {
      task[i].complete = true;
    }
  }
  
}


The last part is really important, and addresses the Phantom Task issue from before: when you remove an item from a ListBox, the index for each item in that ListBox changes. If you have a Quest called "Go to Work" with an ID of 0, and a Quest called "Go Home" with an ID of 1, and then mark "Go to Work" as Complete...the selected Index value of "Go Home" is 0 on the ListBox, which will confuse all of the fancy lookup scripts that we've put together. This is gross, and sucks, but the solution is basically to adjust the index value to compensate for the amount of quests that are now marked as Completed.

So, basically, that's it! I apologize for the eyebleed here. In the future, I'd like to offer some kind of template that makes this all super easy, so that other AGS devs can have fun building their own quests with it.
#20
Quote from: Crimson Wizard on Sat 08/01/2022 06:46:31
I'm sorry, I did not have time to look into this yet, and I definitely should have written a better documentation.
Hey, it's totally okay - again, I appreciate what you've made, and am getting great use out of it!

That switch example is nice - I didn't even know about those!
SMF spam blocked by CleanTalk