Java-Gaming.org Hi !
Featured games (81)
games approved by the League of Dukes
Games in Showcase (513)
Games in Android Showcase (119)
games submitted by our members
Games in WIP (575)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  DirectAndRawInputEnvironmentPlugin not recognizing events or giving polldata  (Read 1722 times)
0 Members and 1 Guest are viewing this topic.
Offline Nomac

Junior Newbie





« Posted 2014-07-21 21:26:47 »

Hi !
I've been using the getDefaultEnvironment().getControllers() to get the controllers before and everyone was fine (either using event to read the input or use the polldata of each component). But since I want to verify if the controller is plugged in or not, it seems that I need to use the DirectAndRawInputEnvironmentPlugin() instead. So I did a quick switch and it's not working. My event queue is never triggered and the polldata of each component is always 0. Here the code of the two solutions I tried:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
202  
203  
204  
205  
206  
207  
208  
209  
210  
211  
212  
213  
214  
215  
216  
217  
218  
219  
220  
221  
222  
223  
224  
225  
226  
227  
228  
229  
230  
231  
232  
233  
234  
235  
236  
237  
238  
239  
240  
241  
242  
243  
244  
245  
246  
247  
248  
249  
250  
251  
252  
253  
254  
255  
256  
257  
258  
259  
260  
261  
262  
263  
264  
265  
266  
267  
268  
269  
270  
271  
272  
import javax.swing.Timer;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import net.java.games.input.*;
import net.java.games.input.Component;
import net.java.games.input.Component.Identifier;
import net.java.games.input.Controller;
import net.java.games.input.ControllerEnvironment;
import net.java.games.input.DirectAndRawInputEnvironmentPlugin;
import net.java.games.input.Event;
import net.java.games.input.EventQueue;


public class detect_all_bouttons {
   
   boutton_control_selection selection;
   Timer le_timer;
   Controller[] controllers;
   
   public detect_all_bouttons(boutton_control_selection la_section){
      selection = la_section;
     
      ActionListener actionlistener= new ActionListener()
      {
          public void actionPerformed(ActionEvent e)
          {  
             Controller[] controllers_temp;
             DirectAndRawInputEnvironmentPlugin directEnv = new DirectAndRawInputEnvironmentPlugin();
             
             if (directEnv.isSupported()) {
                controllers_temp = directEnv.getControllers();
            } else {
               controllers_temp = ControllerEnvironment.getDefaultEnvironment().getControllers();
            }

             if(controllers == null)
             {
                if (directEnv.isSupported()) {
                   controllers = directEnv.getControllers();
                }    else {
                   controllers = ControllerEnvironment.getDefaultEnvironment().getControllers();
                }
             }
             else if(controllers.length != controllers_temp.length)
             {
           
                if (directEnv.isSupported()) {
                   controllers = directEnv.getControllers();
                }    else {
                   controllers = ControllerEnvironment.getDefaultEnvironment().getControllers();
                }
             }
             
           
            StringBuffer buffer = null;

            for (int i = 0; i < controllers.length; i++) {
               /* Remember to poll each one */
               
               if(controllers[i].getType()!=Controller.Type.MOUSE)
               {
                 
                 
                  controllers[i].poll();
                  Component[] components = controllers[i].getComponents();
                  for(int x=0; x < components.length; x++) {
                     controllers[i].poll();
                                                       //always getting a 0 for PollData even though the key is pressed
                     if(components[x].getPollData() > 0)
                     {
                        selection.effacer_le_input(controllers[i].getName(), components[x].getName(), i);
                        le_timer.stop();
                        return;
                     }
                  }
                 
                 
                 
                  /* Get the controllers event queue */
                  controllers[i].poll();
                  EventQueue queue = controllers[i].getEventQueue();
                  /* Create an event object for the underlying plugin to populate */
                  Event event = new Event();
                  //System.out.println(queue);
                  /* For each object in the queue */
                  while (queue.getNextEvent(event)) {
                                                       //The application never goes into the loop
                     /*
                     * Create a strug buffer and put in it, the controller name,
                     * the time stamp of the event, the name of the component
                     * that changed and the new value.
                     *
                     * Note that the timestamp is a relative thing, not
                     * absolute, we can tell what order events happened in
                     * across controllers this way. We can not use it to tell
                     * exactly *when* an event happened just the order.
                     */

                     buffer = new StringBuffer(controllers[i].getName());
                     buffer.append(" : ");
                     
                     Component comp = event.getComponent();
                     float value = event.getValue();
                     boolean stick_dead_zone = true;
                     String texte_manette = null;
                     
                     
                     if(comp.getName().equals("X Axis") || comp.getName().equals("Y Axis"))
                     {
                        String le_texte = "left joystick";
                        if(value > 0.5 && comp.getName().equals("X Axis"))
                        {
                           texte_manette = "Right on the left joystick";
                        }
                        else if(value < -0.5 && comp.getName().equals("X Axis"))
                        {
                           texte_manette = "Left on the left joystick";
                        }
                        else if(value > 0.5 && comp.getName().equals("Y Axis"))
                        {
                           texte_manette = "Down on the left joystick";
                        }
                        else if(value < -0.5 && comp.getName().equals("Y Axis"))
                        {
                           texte_manette = "Up on the left joystick";
                        }
                        else
                        {
                           stick_dead_zone = false;
                        }
                     }
                     else if(comp.getName().equals("X Rotation") || comp.getName().equals("Y Rotation"))
                     {
                        String le_texte = "right joystick";
                        if(value > 0.5 && comp.getName().equals("X Rotation"))
                        {
                           texte_manette = "Right on the right joystick";
                        }
                        else if(value < -0.5 && comp.getName().equals("X Rotation"))
                        {
                           texte_manette = "Left on the right joystick";
                        }
                        else if(value > 0.5 && comp.getName().equals("Y Rotation"))
                        {
                           texte_manette = "Down on the right joystick";
                        }
                        else if(value < -0.5 && comp.getName().equals("Y Rotation"))
                        {
                           texte_manette = "Up on the right joystick";
                        }
                        else
                        {
                           stick_dead_zone = false;
                        }
                     }
                     else if((comp.getName().equals("Z Rotation") && controllers[i].getName().contains("Controller") != true) || (comp.getName().equals("Z Axis") && controllers[i].getName().contains("Controller") != true))
                     {
                           String le_texte = "right joystick";

                           if(value > 0.5 && comp.getName().equals("Z Axis"))
                           {
                              texte_manette = "Right on the right joystick";
                           }
                           else if(value < -0.5 && comp.getName().equals("Z Axis"))
                           {
                              texte_manette = "Left on the right joystick";
                           }
                           else if(value > 0.5 && comp.getName().equals("Z Rotation"))
                           {
                              texte_manette = "Down on the right joystick";
                           }
                           else if(value < -0.5 && comp.getName().equals("Z Rotation"))
                           {
                              texte_manette = "Up on the right joystick";
                           }
                           else
                           {
                              stick_dead_zone = false;
                           }
                     }
                     else if(comp.getName().equals("Hat Switch"))
                     {
                        Identifier identifier = Component.Identifier.Axis.POV;

                        if(controllers[i].getComponent(identifier).getPollData() == 1)
                        {
                           texte_manette = "Left on the d-pad";
                        }
                        else if(controllers[i].getComponent(identifier).getPollData() == 0.75)
                        {
                           texte_manette = "Down on the d-pad";
                        }
                        else if(controllers[i].getComponent(identifier).getPollData() == 0.25)
                        {
                           texte_manette = "Up on the d-pad";
                        }
                        else if(controllers[i].getComponent(identifier).getPollData() == 0.5)
                        {
                           texte_manette = "Right on the d-pad";
                        }
                        else
                        {
                           stick_dead_zone = false;
                        }
                     }
                     
                     else if(comp.getName().equals("Z Axis"))
                     {

                        if(value > 0.5)
                        {
                           texte_manette = "Left trigger";
                        }
                        else if(value < -0.5)
                        {
                           texte_manette = "Right trigger";
                        }
                        else
                        {
                           stick_dead_zone = false;
                        }
                     }
                     
                     else
                     {
                        buffer.append(comp.getName()).append(" changed to ");
                     }
                     

                     /*
                      * Check the type of the component and display an
                      * appropriate value
                      */

                     if (comp.isAnalog()) {
                           buffer.append(value);
                     }
                     else {
                        if (value == 1.0f) {
                              buffer.append("On");
                        }
                        else {
                           buffer.append("Off");
                        }
                     }
                     
                     if(stick_dead_zone)
                     {
                     
                        //finir_loop = false;
                        if(texte_manette != null)
                        {
                           selection.effacer_le_input(controllers[i].getName(), texte_manette, i);
                        }
                        else
                        {
                           selection.effacer_le_input(controllers[i].getName(), comp.getName(), i);
                        }
                        le_timer.stop();
                        return;
                     }
                  }
               }
            }
          }
      };
      le_timer = new Timer(10, actionlistener);
      le_timer.start();
   
   }

}


I'm a bit at a loss at this point. I'm using Eclipse for my project and I'm using Windows 8.
Offline gouessej
« Reply #1 - Posted 2014-07-21 22:03:27 »

Hi

Which version of JInput do you use? Which controller do you use for your tests? I rarely use JInput but the last time I had to detect if a controller was plugged or not, I just had to use a listener, it was very simple and portable. You have to force the scan in the legacy version, otherwise it occurs only once in the default environment.

Offline Nomac

Junior Newbie





« Reply #2 - Posted 2014-07-23 16:26:33 »

I just switched to a newer version of Jinput and I'm still not getting any results. I currently tried with a USB keyboard, but I used various controllers before (X360, PS3 Afterflow, Mad Catz stick). Like I said, everything was working perfectly fine until I decided to use DirectAndRawInputEnvironmentPlugin() which is supposed to be checking the controllers plugged in at all time. It does seem to detect the controllers and keyboards, but I'm not getting any response from any button pressed afterward.

EDIT:
Like suggested in some other post, I changed DirectAndRawInputEnvironmentPlugin for a new constructor instead of DefaultEnvironment. The good news is that it does detect the controllers and keyboard being plugged or unplugged. But for some reasons, it doesn't want to detect any keyboard input. Seems a bit odd that it would detect my X360 controller, but not my keyboard input at all. I saw on some posts that sometimes you need to change the focus, would that be the reason that I always get a polldata of 0 and no event detection from the keyboard input ?

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
private static ControllerEnvironment createDefaultEnvironment() throws ReflectiveOperationException {

       // Find constructor (class is package private, so we can't access it directly)
       Constructor<ControllerEnvironment> constructor = (Constructor<ControllerEnvironment>)
           Class.forName("net.java.games.input.DefaultControllerEnvironment").getDeclaredConstructors()[0];

       // Constructor is package private, so we have to deactivate access control checks
       constructor.setAccessible(true);

       // Create object with default constructor
       return constructor.newInstance();
   }
   
   static {

       AccessController.doPrivileged(new PrivilegedAction<Object>() {
           public Object run() {
               String os = System.getProperty("os.name", "").trim();
               if (os.startsWith("Windows 8")) {  // 8, 8.1 etc.

                   // disable default plugin lookup
                   System.setProperty("jinput.useDefaultPlugin", "false");

                   // set to same as windows 7 (tested for windows 8 and 8.1)
                   System.setProperty("net.java.games.input.plugins", "net.java.games.input.DirectAndRawInputEnvironmentPlugin");

               }
               return null;
           }
       });

   }
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline gouessej
« Reply #3 - Posted 2014-07-24 11:01:48 »

Do you use this code in an AWT application?

Offline Nomac

Junior Newbie





« Reply #4 - Posted 2014-07-24 16:36:04 »

Yes I do use various JFrame and JPanel for the interface. At the moment of the keyboard input, there is a Jpanel being displayed on the entire screen (to indicate that the User must press a button). This JPanel is set to visible false normally, but upon starting this function, the JPanel is set to visible true. I also tried plugging different keyboards (in case mine could be broken), but still no results. I tried changing the focus with requestFocus() and requestFocusInWindow() by setting it up on the main JFrame, but that didn't work either.

EDIT, finally found the solution:

The main problem is new DirectAndRawInputEnvironmentPlugin() which is deleting all the event queue for a keyboard when you create a new one (even if you create a second variable). For now, the solution I found was to create a new variable with a timer to avoid re-creating the DirectAndRawInputEnvironmentPlugin() variable to often and it seems to work. Also, I used poll() to detect if a controller is unplugged, when a controller is unplugged, I re-created the DirectAndRawInputEnvironmentPlugin() variable. Feel free to use my code for those in the future that would need to solve this problem:

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
113  
114  
115  
116  
117  
118  
119  
120  
121  
122  
123  
124  
125  
126  
127  
128  
129  
130  
131  
132  
133  
134  
135  
136  
137  
138  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172  
173  
174  
175  
176  
177  
178  
179  
180  
181  
182  
183  
184  
185  
186  
187  
188  
189  
190  
191  
192  
193  
194  
195  
196  
197  
198  
199  
200  
201  
202  
203  
204  
205  
206  
207  
208  
209  
210  
211  
212  
213  
214  
215  
216  
217  
218  
219  
220  
221  
222  
223  
224  
225  
226  
227  
228  
229  
230  
231  
232  
233  
234  
235  
236  
237  
238  
239  
240  
241  
import javax.swing.JFrame;
import javax.swing.Timer;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import net.java.games.input.*;
import net.java.games.input.Component.Identifier;


public class detect_all_bouttons extends JFrame{
   
   boutton_control_selection selection;
   Timer le_timer;
   Controller[] controllers;
   Controller[] controllers_temp;
   creation_frame fenetre_principale;
   DirectAndRawInputEnvironmentPlugin directEnv = new DirectAndRawInputEnvironmentPlugin();
   int le_temps = 0;
   boolean la_reponse = true;
   
   public detect_all_bouttons (boutton_control_selection la_section, creation_frame la_fenetre){
      selection = la_section;
      Boolean finir_loop = true;
      fenetre_principale = la_fenetre;
     
      ActionListener actionlistener= new ActionListener()
      {
          public void actionPerformed(ActionEvent e)
          {  
             if(controllers == null)
             {
                   controllers = directEnv.getControllers();
             }

                if(la_reponse == false || le_temps == 10)
                {
                   directEnv = new DirectAndRawInputEnvironmentPlugin();
                   controllers = directEnv.getControllers();
                   le_temps = 0;
                }
               
                le_temps++;
                la_reponse = true;
             
            StringBuffer buffer = null;
           
            for (int i = 0; i < controllers.length; i++) {

               if(controllers[i].getType()!=Controller.Type.MOUSE)
               {
                 
                     EventQueue queue = controllers[i].getEventQueue();
                  /* Create an event object for the underlying plugin to populate */
                     Event event = new Event();
                 
                  /* For each object in the queue */
                     if(controllers[i].poll() == true)
                     {
                        controllers[i].poll();
                           while (queue.getNextEvent(event)) {

                                 buffer = new StringBuffer(controllers[i].getName());
                                 buffer.append(" : ");
                     
                                 Component comp = event.getComponent();
                                 float value = event.getValue();
                                 boolean stick_dead_zone = true;
                                 String texte_manette = null;
                     
                     
                                 if(comp.getName().equals("X Axis") || comp.getName().equals("Y Axis"))
                                 {
                                    String le_texte = "left joystick";
                                    if(value > 0.5 && comp.getName().equals("X Axis"))
                                    {
                                       texte_manette = "Right on the left joystick";
                                    }
                                    else if(value < -0.5 && comp.getName().equals("X Axis"))
                                    {
                                       texte_manette = "Left on the left joystick";
                                    }
                                    else if(value > 0.5 && comp.getName().equals("Y Axis"))
                                    {
                                       texte_manette = "Down on the left joystick";
                                    }
                                    else if(value < -0.5 && comp.getName().equals("Y Axis"))
                                    {
                                       texte_manette = "Up on the left joystick";
                                    }
                                    else
                                    {
                                       stick_dead_zone = false;
                                    }
                                 }
                                 else if(comp.getName().equals("X Rotation") || comp.getName().equals("Y Rotation"))
                                 {
                                    String le_texte = "right joystick";
                                    if(value > 0.5 && comp.getName().equals("X Rotation"))
                                    {
                                       texte_manette = "Right on the right joystick";
                                    }
                                    else if(value < -0.5 && comp.getName().equals("X Rotation"))
                                    {
                                       texte_manette = "Left on the right joystick";
                                    }
                                    else if(value > 0.5 && comp.getName().equals("Y Rotation"))
                                    {
                                       texte_manette = "Down on the right joystick";
                                    }
                                    else if(value < -0.5 && comp.getName().equals("Y Rotation"))
                                    {
                                       texte_manette = "Up on the right joystick";
                                    }
                                    else
                                    {
                                       stick_dead_zone = false;
                                    }
                                 }
                                 else if((comp.getName().equals("Z Rotation") && controllers[i].getName().contains("Controller") != true) || (comp.getName().equals("Z Axis") && controllers[i].getName().contains("Controller") != true))
                                 {
                                    String le_texte = "right joystick";

                                    if(value > 0.5 && comp.getName().equals("Z Axis"))
                                    {
                                       texte_manette = "Right on the right joystick";
                                    }
                                    else if(value < -0.5 && comp.getName().equals("Z Axis"))
                                    {
                                       texte_manette = "Left on the right joystick";
                                    }
                                    else if(value > 0.5 && comp.getName().equals("Z Rotation"))
                                    {
                                       texte_manette = "Down on the right joystick";
                                    }
                                    else if(value < -0.5 && comp.getName().equals("Z Rotation"))
                                    {
                                       texte_manette = "Up on the right joystick";
                                    }
                                    else
                                    {
                                       stick_dead_zone = false;
                                    }
                                 }
                                 else if(comp.getName().equals("Hat Switch"))
                                 {
                                    Identifier identifier = Component.Identifier.Axis.POV;

                                    if(controllers[i].getComponent(identifier).getPollData() == 1)
                                    {
                                       texte_manette = "Left on the d-pad";
                                    }
                                    else if(controllers[i].getComponent(identifier).getPollData() == 0.75)
                                    {
                                       texte_manette = "Down on the d-pad";
                                    }
                                    else if(controllers[i].getComponent(identifier).getPollData() == 0.25)
                                    {
                                       texte_manette = "Up on the d-pad";
                                    }
                                    else if(controllers[i].getComponent(identifier).getPollData() == 0.5)
                                    {
                                       texte_manette = "Right on the d-pad";
                                    }
                                    else
                                    {
                                       stick_dead_zone = false;
                                    }
                                 }
                     
                                 else if(comp.getName().equals("Z Axis"))
                                 {

                                    if(value > 0.5)
                                    {
                                       texte_manette = "Left trigger";
                                    }
                                    else if(value < -0.5)
                                    {
                                       texte_manette = "Right trigger";
                                    }
                                    else
                                    {
                                       stick_dead_zone = false;
                                    }
                                 }
                     
                                 else
                                 {
                                    buffer.append(comp.getName()).append(" changed to ");
                                 }
                     

                     /*
                      * Check the type of the component and display an
                      * appropriate value
                      */

                                 if (comp.isAnalog()) {
                                    buffer.append(value);
                                 }
                                 else {
                                    if (value == 1.0f) {
                                       buffer.append("On");
                                    }
                                    else {
                                       buffer.append("Off");
                                    }
                                 }
                     
                                 if(stick_dead_zone)
                                 {
                     
                                    //finir_loop = false;
                                    if(texte_manette != null)
                                    {
                                       selection.effacer_le_input(controllers[i].getName(), texte_manette, i);
                                    }
                                    else
                                    {
                                       selection.effacer_le_input(controllers[i].getName(), comp.getName(), i);
                                    }
                                    le_timer.stop();
                                    return;
                                 }
                           }
                     }
                     else
                     {
                        System.out.println(controllers[i].getName());
                        la_reponse = false;  
                     }
                  }
                 
               }
             }
      };
      le_timer = new Timer(100, actionlistener);
      le_timer.start();
   
   }
}
Pages: [1]
  ignore  |  Print  
 
 

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

Longarmx (35 views)
2014-10-17 03:59:02

Norakomi (26 views)
2014-10-16 15:22:06

Norakomi (24 views)
2014-10-16 15:20:20

lcass (26 views)
2014-10-15 16:18:58

TehJavaDev (50 views)
2014-10-14 00:39:48

TehJavaDev (52 views)
2014-10-14 00:35:47

TehJavaDev (40 views)
2014-10-14 00:32:37

BurntPizza (63 views)
2014-10-11 23:24:42

BurntPizza (36 views)
2014-10-11 23:10:45

BurntPizza (76 views)
2014-10-11 22:30:10
Understanding relations between setOrigin, setScale and setPosition in libGdx
by mbabuskov
2014-10-09 22:35:00

Definite guide to supporting multiple device resolutions on Android (2014)
by mbabuskov
2014-10-02 22:36:02

List of Learning Resources
by Longor1996
2014-08-16 10:40:00

List of Learning Resources
by SilverTiger
2014-08-05 19:33:27

Resources for WIP games
by CogWheelz
2014-08-01 16:20:17

Resources for WIP games
by CogWheelz
2014-08-01 16:19:50

List of Learning Resources
by SilverTiger
2014-07-31 16:29:50

List of Learning Resources
by SilverTiger
2014-07-31 16:26:06
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!