Inventory: costume items, resetting cursor (both solved)

Started by Sparky, Sat 10/03/2007 19:52:47

Previous topic - Next topic

Sparky

Hello everyone,
I've got a couple of inventory related questions. For reference this project uses no verb buttons- left click is multifunction and right click is look.

Question 1
Currently a left click on an inventory item sets the cursor mode to 4 (use inventory) and sets ActiveInventory to that item. That's fine and dandy most of the time.

For certain items I'd like to alter the default behavior. I'd like a single click on a costume item to automatically change the player's outfit. So if the player were to left click on "sombrero," the character would put on a sombrero instead of changing the active inventory item to "sombrero". How might one go about this?

Question 2
When the player uses an inventory item on a target, I'd like the cursor to return to the normal mode. I've got a function written that changes the cursor mode and resets all the necessart variables. I've been trying to call this using the on_mouse_click script, but I seem to be going about this wrong. Currently if the player uses an inventory item on a target the cursor resets before the interaction with the target object/hotspot/player happens. So by the time the target receives the click we aren't in "use inventory" mode anymore. The cursor resets, but all "use inventory item on" actions are impossible. How can I get around this?

Ashen

1:
Maybe something like the code in this thread? Only instead of changing the cursor mode, you'd change the character View, and clear the ActiveInventory.

Another solution would be to use a mode other than eModeInteract in the inventory (so it doesn't run the default 'set ActiveInventory' interaction), give the Costume Items Interactions in that mode that will change the View, and have unhandled_event set any other Items as active.

2:
What code have you got so far? It might be as simple as juggling the order of the commands in on_mouse_click, but it'd be easier to see what's there, than to have to start from scratch if it half works already.
I know what you're thinking ... Don't think that.

Sparky

#2
Thanks for the leads, here's a progress update:
Question 1
I've set up repeatedly_execute to do the costume change, which will work for the time being. Is there a more elegant solution? This is graphically glitchy (The item appears as the cursor for a moment before repeatedly_execute catches it). I have a hunch it could be done once per click rather than in repeatedly_execute.
Code: ags
	// within repeatedly_execute
	if ((player.ActiveInventory == iDivinghelmet) || (player.ActiveInventory == iLantern)) {
	  player.SayBackground("The costume switch will happen at this point.");
	  //costume switch code will go here
	  reset_left_click();
	}


Question 2
Here's the function that resets the cursor:
Code: ags
function reset_left_click () {
	player.ActiveInventory = null;
	Mouse.Mode = eModeInteract;	
}

And here's the portion of the on_mouse_click script that calls it:
Code: ags
//script for other verbs goes here
else if (player.ActiveInventory != null) { // if we're in "use inventory item" mode
	// currently doesn't seem to work
	player.Say("Now we're over an inventory item.");
	ProcessClick(mouse.x, mouse.y, 2);
	reset_left_click();
}
The reset function seems to reset the cursor correctly, but it's impossible to use in inventory item on anything.

Ashen

#3
1:
Quote
Is there a more elegant solution? This is graphically glitchy (The item appears as the cursor for a moment before repeatedly_execute catches it). I have a hunch it could be done once per click rather than in repeatedly_execute.

I think my other suggestion (the one with unhandled_event) will sort that.

2:
EDIT: OK, you changed the question a little, not sure if this answer still makes sense...
There're two reasons the code wouldn't be run. Firstly, unless you've checked the 'Handle Inventory clicks in script' option, on_mouse_click isn't run for Inventory clicks. Secondly, even if you've got that option checked, it's possible on_mouse_click isn't running anyway - if the Inventory GUI is Popup Modal, the game is paused and the first bit of on_mouse_click stops anything from happening:
Code: ags

  if (IsGamePaused() == 1) {
    // Game is paused, so do nothing (ie. don't allow mouse click)
  }


Put the Inventory code inside that condition, and it should work:
Code: ags

  if (IsGamePaused() == 1) {
    if (button == eMouseLeftInv) {
      InventoryItem *theItem = InventoryItem.GetAtScreenXY(mouse.x,  mouse.y);
      player.Say("Now we're over an inventory item.");
      ProcessClick(mouse.x, mouse.y, 2);
      reset_left_click();
    }
  }

However, ProcessClick probably isn't what you want here:
Quote from: The Manual
NOTE: This function ignores all interfaces and acts as though the point is directly visible. In other words, if the co-ordinates you pass happen to lie on a button on an interface, what actually happens will be as if the user clicked behind the interface onto the actual screen.

So, it'll actually be using eModeInteract on whatever's behind the GUI, not on the item. You want InventoryItem.RunInteraction instead.
I know what you're thinking ... Don't think that.

Sparky

Question 1
I'll try using "talk to" instead of "interact with" and reply once I've got new results.
Question 2
To clarify, the inventory isn't a popup. It's always visible (as in Monkey Island 2 or Day of the Tentacle). Your second suggestion is spot on. I'll try substituting InventoryItem.RunInteraction and see where we stand.

Thank you once again for your help, this project would be nowhere without the patience of people on this board.

Sparky

Costume inventory items
OK, here's what I've got so far.
Here's the relevant part of the on_mouse_click section:
Code: ags
		InventoryItem *target_inventoryitem = InventoryItem.GetAtScreenXY(mouse.x, mouse.y);
		// various checks for other types of interactions- hotspots, objects, etc.
		else if (target_inventoryitem != null) { // if we're over an inventory item
			// currently doesn't seem to work
			player.Say("Now we're over an inventory item.");
			target_inventoryitem.RunInteraction(3); // simulate a click on the target item with the "talk to" verb
		}

And here's the unhandled_event section.
Code: ags
function unhandled_event(int what, int type) {
	if (what == 5 && type == 2) { // talk to inventory
		player.Say("I'm reading a click on the inventory in speak mode.");
		InventoryItem *target_inventoryitem = InventoryItem.GetAtScreenXY(mouse.x,  mouse.y); // use it as the cursor
		player.Say("I'm reading a click on %s.", target_inventoryitem);
		player.ActiveInventory = target_inventoryitem;
	}
}

I'm pretty sure I must not have checked "Handle inventory clicks in script" because this doesn't seem to be working at all. None of the player.Say calls go through. Is it too late to change "Handle inventory clicks in script"? I see it in the manual under "setting up the game", but I don't see a way to change it post-creation.

Resetting the cursor
OK, here's the script that's called when the player uses an inventory item on an object:
Code: ags
		target_type = GetLocationType(mouse.x, mouse.y);
		// if we're over an object
		if (target_type == eLocationObject) {
			player.SayBackground("I see an object.");
			// use inventory
			if (player.ActiveInventory != null) {
				ProcessClick(mouse.x, mouse.y, 4);
				reset_left_click();
			}
		}
	
ProcessClick alone works. It uses the current inventory item on the target object. Also reset_left_click on its own works.

This is where it gets slightly confusing- I've placed a couple of player.Say calls to better understand what's going on. When I call the two functions in series (as above), reset_left_click resolves first. Then, after player.activeInventory has been set to "null", the ProcessClick call goes through. It has no effect because the inventory item has been discarded. It looks like this problem has boiled down to getting these functions to resolve in the desired order.

I don't know much about threads, but I think the solution to the problem might have something to do with what thread is running which function. Am I on the right track here?

Thanks for putting up with my floundering about. I'm hardly adept when it comes to scripting, and it's been very helpful.

Ashen

Quote
Is it too late to change "Handle inventory clicks in script"? I see it in the manual under "setting up the game", but I don't see a way to change it post-creation.

What do you mean by 'post-creation'? 'Handle inventory clicks in script' is a check box on the General Settings pane of the editor (this is what ALL the options in the 'Game Options' bit of the maual refer to). You can change it as often as you like, up to the point you produce the final, compiled exe. You can also (as the manual says) change it via script, using the 'OPTHANDLEINVCLICKS' parameter of SetGameOption - but I don't really recommend this. There's no option to set it at the creation of the game (when you set the name, resolution, etc), it's just off until you turn it on.
However, whether that's checked or not shouldn't affect unhandled_event running - it runs OK for me with or without it - excpet for the line:
Code: ags

  player.Say("I'm reading a click on %s.", target_inventoryitem);

which should be:
Code: ags

  player.Say("I'm reading a click on %s.", target_inventoryitem.Name);


There must be something else at play - try adding a check of which Mode is being used, e.g.:
Code: ags

function unhandled_event(int what, int type) {
  if (what == 5) { // Any Inv click
    Display"Mode = %d", mouse.Mode);
  }
}

Quote
This is where it gets slightly confusing- I've placed a couple of player.Say calls to better understand what's going on. When I call the two functions in series (as above), reset_left_click resolves first. Then, after player.activeInventory has been set to "null", the ProcessClick call goes through. It has no effect because the inventory item has been discarded. It looks like this problem has boiled down to getting these functions to resolve in the desired order.
I think it's as you said - it finishes the on_mouse_click function (which clears the active inventory) before starting the 'Room thread' which contains the Object interaction. (Actually, I don't think that's quite how it works, but it's as good a way to think about it as any...)

You could try clearing the acitve inventory from the interaction code, but tha'd be a fair amount of work to add to every interaction in the game. easier might be to use a Timer - trigger it in on_mouse_click instead of running reset_left_click(), and use IsTimerExpired to reset the inventory.
I know what you're thinking ... Don't think that.

Sparky

Costume inventory items
I'm mightily embarrassed- it all boiled down to enabling "handle inventory clicks in script". Once I did that I just needed to add a section in on_mouse_click that handles the inventory clicks
Code: ags
	else if (button == eMouseLeftInv) { // left click within inventory gui over an item
		// player.Say("Now we're over an inventory item.");
		target_inventoryitem.RunInteraction(3); // simulate a click on the target item with the "talk to" verb		
	}

Everything is working as desired now. For future reference, the script in the previous post works with the exception of player.Say call Ashen pointed out, which now looks like
Code: ags
		player.Say("I'm reading a click on the %s.", target_inventoryitem.Name);


Resetting the Cursor
I've experimented with calling reset_left_click from within object/hotspot/character interactions in the past. It works, but as you said it's not a very good solution. So I tried your timer suggestion. The timer is set within the on_mouse_click section every time the player clicks in use inventory mode.
Code: ags
			if (player.ActiveInventory != null) {
				ProcessClick(mouse.x, mouse.y, 4);
				SetTimer(1, 10); // Here's where the reset timer is set
			}
That seems to be enough time for the ProcessClick call to resolve. Then in the repeatedly_execute section the timer is checked and the cursor resets after a brief pause.
Code: ags
	// begin reset timer script
	if (IsTimerExpired(1)) {
	  reset_left_click();
	}
Both of my problems are now solved! Thank you for your patience with my underdeveloped scripting skills. I'll repay the debt by working hard to make a good game and answering questions in the Beginners' Technical Forum!

SMF spam blocked by CleanTalk