001/*
002 * Forge Mod Loader
003 * Copyright (c) 2012-2013 cpw.
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser Public License v2.1
006 * which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
008 * 
009 * Contributors:
010 *     cpw - implementation
011 */
012
013package cpw.mods.fml.client.registry;
014
015import java.util.ArrayList;
016import java.util.EnumSet;
017import java.util.Set;
018
019import net.minecraft.client.settings.GameSettings;
020import net.minecraft.client.settings.KeyBinding;
021
022import org.lwjgl.input.Keyboard;
023import org.lwjgl.input.Mouse;
024
025import com.google.common.collect.Lists;
026import com.google.common.collect.Sets;
027
028import cpw.mods.fml.common.ITickHandler;
029import cpw.mods.fml.common.TickType;
030import cpw.mods.fml.common.registry.TickRegistry;
031import cpw.mods.fml.relauncher.Side;
032
033public class KeyBindingRegistry
034{
035    /**
036     * Register a KeyHandler to the game. This handler will be called on certain tick events
037     * if any of its key is inactive or has recently changed state
038     *
039     * @param handler
040     */
041    public static void registerKeyBinding(KeyHandler handler) {
042        instance().keyHandlers.add(handler);
043        if (!handler.isDummy)
044        {
045            TickRegistry.registerTickHandler(handler, Side.CLIENT);
046        }
047    }
048
049
050    /**
051     * Extend this class to register a KeyBinding and recieve callback
052     * when the key binding is triggered
053     *
054     * @author cpw
055     *
056     */
057    public static abstract class KeyHandler implements ITickHandler
058    {
059        protected KeyBinding[] keyBindings;
060        protected boolean[] keyDown;
061        protected boolean[] repeatings;
062        private boolean isDummy;
063
064        /**
065         * Pass an array of keybindings and a repeat flag for each one
066         *
067         * @param keyBindings
068         * @param repeatings
069         */
070        public KeyHandler(KeyBinding[] keyBindings, boolean[] repeatings)
071        {
072            assert keyBindings.length == repeatings.length : "You need to pass two arrays of identical length";
073            this.keyBindings = keyBindings;
074            this.repeatings = repeatings;
075            this.keyDown = new boolean[keyBindings.length];
076        }
077
078
079        /**
080         * Register the keys into the system. You will do your own keyboard management elsewhere. No events will fire
081         * if you use this method
082         *
083         * @param keyBindings
084         */
085        public KeyHandler(KeyBinding[] keyBindings)
086        {
087            this.keyBindings = keyBindings;
088            this.isDummy = true;
089        }
090
091        public KeyBinding[] getKeyBindings()
092        {
093            return this.keyBindings;
094        }
095
096        /**
097         * Not to be overridden - KeyBindings are tickhandlers under the covers
098         */
099        @Override
100        public final void tickStart(EnumSet<TickType> type, Object... tickData)
101        {
102            keyTick(type, false);
103        }
104
105        /**
106         * Not to be overridden - KeyBindings are tickhandlers under the covers
107         */
108        @Override
109        public final void tickEnd(EnumSet<TickType> type, Object... tickData)
110        {
111            keyTick(type, true);
112        }
113
114        private void keyTick(EnumSet<TickType> type, boolean tickEnd)
115        {
116            for (int i = 0; i < keyBindings.length; i++)
117            {
118                KeyBinding keyBinding = keyBindings[i];
119                int keyCode = keyBinding.keyCode;
120                boolean state = (keyCode < 0 ? Mouse.isButtonDown(keyCode + 100) : Keyboard.isKeyDown(keyCode));
121                if (state != keyDown[i] || (state && repeatings[i]))
122                {
123                    if (state)
124                    {
125                        keyDown(type, keyBinding, tickEnd, state!=keyDown[i]);
126                    }
127                    else
128                    {
129                        keyUp(type, keyBinding, tickEnd);
130                    }
131                    if (tickEnd)
132                    {
133                        keyDown[i] = state;
134                    }
135                }
136
137            }
138        }
139
140        /**
141         * Called when the key is first in the down position on any tick from the {@link #ticks()}
142         * set. Will be called subsequently with isRepeat set to true
143         *
144         * @see #keyUp(EnumSet, KeyBinding, boolean)
145         *
146         * @param types the type(s) of tick that fired when this key was first down
147         * @param tickEnd was it an end or start tick which fired the key
148         * @param isRepeat is it a repeat key event
149         */
150        public abstract void keyDown(EnumSet<TickType> types, KeyBinding kb, boolean tickEnd, boolean isRepeat);
151        /**
152         * Fired once when the key changes state from down to up
153         *
154         * @see #keyDown(EnumSet, KeyBinding, boolean, boolean)
155         *
156         * @param types the type(s) of tick that fired when this key was first down
157         * @param tickEnd was it an end or start tick which fired the key
158         */
159        public abstract void keyUp(EnumSet<TickType> types, KeyBinding kb, boolean tickEnd);
160
161
162        /**
163         * This is the list of ticks for which the key binding should trigger. The only
164         * valid ticks are client side ticks, obviously.
165         *
166         * @see cpw.mods.fml.common.ITickHandler#ticks()
167         */
168        public abstract EnumSet<TickType> ticks();
169    }
170
171    private static final KeyBindingRegistry INSTANCE = new KeyBindingRegistry();
172
173    private Set<KeyHandler> keyHandlers = Sets.newLinkedHashSet();
174
175    @Deprecated
176    public static KeyBindingRegistry instance()
177    {
178        return INSTANCE;
179    }
180
181
182    public void uploadKeyBindingsToGame(GameSettings settings)
183    {
184        ArrayList<KeyBinding> harvestedBindings = Lists.newArrayList();
185        for (KeyHandler key : keyHandlers)
186        {
187            for (KeyBinding kb : key.keyBindings)
188            {
189                harvestedBindings.add(kb);
190            }
191        }
192        KeyBinding[] modKeyBindings = harvestedBindings.toArray(new KeyBinding[harvestedBindings.size()]);
193        KeyBinding[] allKeys = new KeyBinding[settings.keyBindings.length + modKeyBindings.length];
194        System.arraycopy(settings.keyBindings, 0, allKeys, 0, settings.keyBindings.length);
195        System.arraycopy(modKeyBindings, 0, allKeys, settings.keyBindings.length, modKeyBindings.length);
196        settings.keyBindings = allKeys;
197        settings.loadOptions();
198    }
199}