;--------------------------------------------------------------------------- ; MChat - a multiuser chat program for TI-85 and ZShell 4.0 ; Version 0.99 (beta) 14.1.1998 ; Copyright (C) 1998 Osma Suominen ;--------------------------------------------------------------------------- ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 2 of the License, or ; (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program; if not, write to the Free Software ; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ;--------------------------------------------------------------------------- #include "ti-85.h" ; Variables in graphics memory: I2CAddress = $8641 I2CInputBuffer = $8901 I2CInputLength = $8642 SendErrors = $8643 ; Send Error counter I2CCStart = $8645 ; Start counter I2CCStop = $8647 ; Stop counter I2CCMiss = $8649 ; Missed Start counter I2CCTimeout = $864B ; Timeout counter I2CCChecksum = $864D ; Checksum error counter I2CSpeed = $864F PRGMLast = $8650 ; Last id character with : after CUSTOMLast = $8651 ; Last id character with > after InfoBuffer = $8652 ; Scratchpad for making Info data NumberScratch = $8680 ; Scratchpad for converting a number OutputBuffer = $8981 ; Variables in text memory KeyFlags = $80DF ; xxx00000 ; bit 5 = Shift ; bit 6 = Num ; bit 7 = Lock OutputLength = $80E0 ; Output string length OutputLimit = $80E1 ; Max length of string CursorX = $80E2 ; X coordinate of blinking cursor CursorBlink = $80E3 ; Cursor blink counter InfoEntries = $80E4 ; Number of Info entries LogScroll = $80E5 ; Counts log scrolling LineScroll = $80E6 ; Counts input line scrolling InfoStart = $80E7 ; Start of Info database ; (takes the rest of text memory) ; Backlog locations FreeMemStart = $8BE1 ; provided by the TI operating system FreeMemEnd = $8BE5 ; LogLength = $0800 ; 16 entries * 128 bytes = 2 kbytes SecondEntry = $0080 LastEntry = $0780 NinthEntry = $0400 OurPid = 111 ; Our protocol id .org 0 .db "MChat v0.99",0 ; 11 chars + $0 ProgStart: ; Total 12 chars, shown by ProgStart ROM_CALL(CLEARLCD) ; Show beginning screen ld hl,0 ld (CURSOR_ROW),hl ; overlaps into CURSOR_COL ld hl,(PROGRAM_ADDR) ld de,TitleText add hl,de ROM_CALL(D_ZT_STR) ; Reserve memory ld hl,(FreeMemStart) ld de,LogLength add hl,de push hl pop de ; DE = end of needed memory ld hl,(FreeMemEnd) ; HL = end of free memory or a sbc hl,de ; HL negative if not enough memory jr c,MemError CALL_(ClearMem) ; Clear the memory ; Determine address of this calc - pick the first unused one ; xor a ld a,127 ld (I2CSpeed),a ; Use speed 0 for determining address ld b,0 ; Ping packet has length 0 ld a,OurPid ; And protocol id != 0 ld d,$7E ; Start at $7E + 2 = $80 = 128 DetLoop: inc d inc d CALL_(MI2C) ; Check for a calc at this address jr nc,DetLoop halt ; Wait for the possible slave to wake up halt CALL_(MI2C) ; Double check to be sure jr nc,DetLoop ld a,d ld (I2CAddress),a ; Now set the actual address push af ld hl,(PROGRAM_ADDR) ld de,AddressText add hl,de ROM_CALL(D_ZT_STR) ; Display my address pop af ld h,0 ld l,a ROM_CALL(D_HL_DECI) CALL_(SI2CLoad) ; Activate slave interrupt routines SpeedSetting: ; Marks this place so we can change it ld a,8 ; 8 is the compile-time default speed ld (I2CSpeed),a ; Set transfer speed (cleared by slave rout.) CALL_(AnyKey) ld a,1 ld (InfoEntries),a ; Now there is one entry in the Info db SetIdNick: xor a ld (OutputLength),a ; Reset output length counter ld (LogScroll),a ; Disable chatlog display ; Fill beg. of output buffer with spaces ld b,8 ; First 8 chars are important ld hl,OutputBuffer SetIdNickLoop: ld (hl),' ' inc hl djnz SetIdNickLoop CALL_(DrawMain) jr Id MemError: ld hl,(PROGRAM_ADDR) ld de,MemErrorText add hl,de ROM_CALL(D_ZT_STR) CALL_(AnyKey) ret Id: ld hl,0 ld (CURSOR_X),hl ; Overlaps into CURSOR_Y ld hl,(PROGRAM_ADDR) ld de,IdText add hl,de ROM_CALL(D_ZM_STR) ; HL at DefId now ld a,1 ld (OutputLimit),a ld a,(hl) ; Check default (saved) id ld (OutputBuffer),a or a jr z,IdLoop ; Default id empty - length 0 ld a,1 ; Default id not empty ld (OutputLength),a ; Set length to 1 CALL_(Disp) ; Display the default id IdLoop: CALL_(Cursor) CALL_(ReadInBuffer) ; Handle incoming packets & delay call GET_KEY cp $9 jr z,Nick CALL_(HandleInput) jr IdLoop Nick: ld a,(OutputLength) or a jr z,IdLoop ; Don't allow an empty id char ld de,InfoStart ; Target: First byte of Info db ld hl,I2CAddress ; Source: Own I2C address ldi ld hl,OutputBuffer ; Source: Own id char ldi CALL_(DrawStatus) ; Show the id char CALL_(Clear) ; Clear input line - and reset OutputLength ld hl,0 ld (CURSOR_X),hl ; Overlaps into CURSOR_Y ld hl,(PROGRAM_ADDR) ld de,DefNick add hl,de ld de,OutputLength ; Move default nick length into OutputLength ldi ld de,OutputBuffer ; Move default nick into OutputBuffer ld bc,8 ldir ROM_CALL(D_ZM_STR) ; NickText comes right after ld a,8 ld (OutputLimit),a CALL_(Disp) ; Display default id NickLoop: CALL_(Cursor) CALL_(ReadInBuffer) ; Handle incoming packets & delay call GET_KEY cp $9 jr z,Main CALL_(HandleInput) jr NickLoop Main: ld a,(OutputLength) or a jr z,NickLoop ; Don't allow an empty nickname ld c,a ; Save OutputLength in C for later use ld hl,InfoStart+2 ; Target: Start of own nickname data push hl ; Save start of own nickname data ld b,8 ; 8 bytes for nickname MainClearNick: ld (hl),' ' inc hl djnz MainClearNick ; Fill nickname data with spaces pop de ; Restore start of own nickname data ld hl,OutputBuffer ; Source: Own nickname ld b,0 ; Length = C = (OutputLength) push hl ; Save HL (own nickname, just entered) push bc ; and BC (length) for later use ldir ld hl,(PROGRAM_ADDR) ld de,DefId add hl,de ; HL = DefId ld a,(InfoStart+1) ld (hl),a ; Save default id inc hl ; HL = DefNick (length byte) pop bc ld (hl),c ; Save nick length inc hl ex de,hl ; DE = DefNick (data bytes) pop hl ldir ; Set entered nick as the default CALL_(DrawStatus) ; Show the nickname in the status line CALL_(Clear) ; Clear the input line ld a,$ff ; ReqInfo protocol id ld de,0 CALL_(SendInfo) ; Broadcast the id & nick to everyone ld a,125 ld (OutputLimit),a ; Set the character limit to 125 MainLoopNew: ld a,8 ld (LogScroll),a ; Enable log screen display CALL_(RedrawLog) ; Draw the chatlog MainLoop: CALL_(Cursor) ; Handle the cursor CALL_(ReadInBuffer) ; Handle incoming packets & delay call GET_KEY or a ; No key pressed jr z,MainLoop cp $1 ; "Down" jr z,Down cp $4 ; "Up" jr z,Up cp $38 ; "More" jr z,More ; Reset both scroll counters push af ld a,(LineScroll) bit 7,a jr nz,MainCont1 ; No scrolling made - continue or $80 ; Set bit 7 of (LineScroll) ld (LineScroll),a ; to reset it into normal (no scroll) mode CALL_(Disp) MainCont1: ld a,(LogScroll) cp 8 jr z,MainCont2 ; No scrolling has been made - continue ld a,8 ld (LogScroll),a ; Restore to normal mode (no scrolling) CALL_(RedrawLog) ; Redraw chatlog MainCont2: pop af cp $9 ; "Enter" jr z,Enter ; Enter sends the line cp $35 ; "F1" JUMP_Z(F1) cp $34 ; "F2" JUMP_Z(F2) cp $33 ; "F3" JUMP_Z(F3) cp $32 ; "F4" JUMP_Z(SetIdNick) ; F4 changes the id and nick cp $1f ; "PRGM" JUMP_Z(PRGM) cp $17 ; "CUSTOM" JUMP_Z(CUSTOM) CALL_(HandleInput) ; Handles other input jr MainLoop ; This code is used only by the main loop Down: ld a,(LogScroll) dec a cp 7 jr UpTail Up: ld a,(LogScroll) inc a UpTail: JUMP_Z(MainLoop) ; Illegal value - don't save ld (LogScroll),a CALL_(RedrawLog) JUMP_(MainLoop) More: ld a,(LineScroll) and $0f dec a ld (LineScroll),a CALL_(Disp) JUMP_(MainLoop) Enter: ; Send a line of text ld a,(OutputLength) or a JUMP_Z(MainLoop) ; Empty line - don't send ; Make a packet & send it ld a,(OutputBuffer+1) ; Second byte of output buffer cp '>' jr z,SendPrivate ld d,0 ; General call cp ':' jr z,SaveToPRGM SendPacket: ld a,(OutputLength) ld b,a ; Data length ld a,OurPid ; Our protocol id ld hl,OutputBuffer CALL_(MI2C) ; Send packet jr nc,SendPacketOK ld hl,(SendErrors) inc hl ld (SendErrors),hl JUMP_(MainLoop) ; Error - return to main loop SendPacketOK: CALL_(LogShift) ; Load contents of output buffer into last entry in log ld hl,(FreeMemStart) ld de,LastEntry add hl,de ld a,(InfoStart+1) ; Our id char ld (hl),a inc hl ld a,(OutputLength) ld (hl),a inc hl ld (hl),0 ; Line count - unknown inc hl ld b,0 ld c,a ; Byte count ex de,hl ; Target: Last log entry, data ld hl,OutputBuffer ; Source: Output buffer ldir CALL_(RedrawLog) CALL_(Clear) ; Clear the input line JUMP_(MainLoop) SendPrivate: ; Determine recipient address ld a,(OutputBuffer) ; First character = id byte ld hl,InfoStart+11 ; 2nd Info entry, id byte (first is ours) CALL_(InfoFind) ld d,0 jr nz,SendPacket ; No match - send as general call ld (CUSTOMLast),a dec hl ; Address byte ld d,(hl) ; Send to this address only jr SendPacket SaveToPRGM: ld a,(OutputBuffer) ; First byte of buffer - id char of recipient ld (PRGMLast),a jr SendPacket ; The function keys (except F4 and F5) F1: ROM_CALL(CLEARLCD) ld hl,0 ld (CURSOR_X),hl ld hl,(PROGRAM_ADDR) ld de,F1Text add hl,de ld b,10 ld c,0 ; X-coordinate 0 CALL_(DispHelp) xor a ld (CURSOR_Y),a ld b,9 ld c,80 ; X-coordinate 80 CALL_(DispHelp) jr FxEnd F2: ROM_CALL(CLEARLCD) ld hl,0 ld (CURSOR_X),hl ld hl,(PROGRAM_ADDR) ld de,F2Text add hl,de ROM_CALL(D_ZM_STR) ld hl,$0600 ld (CURSOR_X),hl ld c,l ; C=0 ld hl,InfoStart ld a,(InfoEntries) cp 9 jr c,F2SingleCol ; Just one column needed ld b,8 ; Display first 8 entries CALL_(F2PrintCol) ; Print a column of info ld a,(InfoEntries) sub 8 ; Display remaining entries ld hl,$0640 ld (CURSOR_X),hl ld c,l ; at X=64 ld hl,InfoStart+80 ; Start at ninth entry F2SingleCol: ld b,a CALL_(F2PrintCol) ; Print a column of info FxEnd: ld hl,$3A50 ; X=80 Y=58 ld (CURSOR_X),hl ld hl,(PROGRAM_ADDR) ld de,PressText add hl,de ROM_CALL(D_ZM_STR) CALL_(AnyKey) FxEndTail: CALL_(DrawMain) JUMP_(MainLoopNew) F3: ROM_CALL(CLEARLCD) ld hl,0 ld (CURSOR_X),hl ld hl,(PROGRAM_ADDR) ld de,F3Text add hl,de ROM_CALL(D_ZM_STR) ld b,6 ld de,SendErrors F3Loop: push bc ld a,(CURSOR_Y) add a,6 ld (CURSOR_Y),a xor a ld (CURSOR_X),a ROM_CALL(D_ZM_STR) ex de,hl push de push hl ld a,64 ld (CURSOR_X),a call LD_HL_MHL CALL_(UnpackNumber) ld b,5 ld hl,NumberScratch ROM_CALL(D_LM_STR) pop hl pop de inc hl ; Advance to the next counter inc hl ex de,hl pop bc djnz F3Loop push hl ld hl,$2A00 ; X=0 Y=42 ld (CURSOR_X),hl pop hl ROM_CALL(D_ZM_STR) ; Already advanced to "Speed:" text push hl ld hl,$330F ; X=15 Y=51 ld (CURSOR_X),hl pop hl ROM_CALL(D_ZM_STR) ; Already advanced to "Change.." text push hl ld hl,$3A50 ld (CURSOR_X),hl pop hl ROM_CALL(D_ZM_STR) ; Already advanced to "Press.." text F3DispSpeed: ld hl,$2A4C ld (CURSOR_X),hl ld hl,I2CSpeed CALL_(DispHex) ; Show the speed CALL_(AnyKey) cp $1 jr z,F3Down cp $4 jr z,F3Up ld hl,(PROGRAM_ADDR) ld de,SpeedSetting+1 ; Byte which sets the default speed add hl,de ld a,(I2CSpeed) ld (hl),a ; Set default speed CALL_(DrawMain) JUMP_(MainLoopNew) F3Up: inc (hl) ; Increment (I2CSpeed) inc (hl) ; This will be reversed in F3Down... F3Down: dec (hl) ; Decrement (I2CSpeed) jr F3DispSpeed PRGM: ld a,(PRGMLast) ld b,':' jr CUSTOMTail CUSTOM: ld a,(CUSTOMLast) ld b,'>' CUSTOMTail: or a JUMP_Z(MainLoop) ; No character saved - return ld hl,OutputBuffer ld (hl),a ; Save character into buffer inc hl ld (hl),b ; Follow with : or > CALL_(Clear) ; Clear output buffer (screen) ld a,2 ld (OutputLength),a ; Set output length to 2 CALL_(Disp) ; Display input line JUMP_(MainLoop) ; Subroutines to handle text input HandleInput: or a ret z ; No key pressed - loop cp $f ; "Clear" JUMP_Z(Clear) cp $20 ; "Del" JUMP_Z(Del) cp $30 ; "Alpha" JUMP_Z(Alpha) cp $36 ; "2nd" JUMP_Z(Second) cp $37 ; "Exit" JUMP_Z(Exit) cp $31 ; "F5" JUMP_Z(F5) cp $0a ; "+" ret c cp $30 ; "Graph"+1 ret nc sub $0a ld hl,(PROGRAM_ADDR) ld de,Scan2Index add hl,de ld d,0 ld e,a add hl,de ld a,(hl) ; a=index value of keypress, 0 to 31 or a ret z ; Invalid key - loop ld hl,KeyFlags or (hl) and $7F ; Strip off lock bit ld hl,(PROGRAM_ADDR) ld de,Index2Ascii add hl,de ld d,0 ld e,a add hl,de ld a,(hl) ; a=ascii value of keypress or a ret z ; Invalid key - loop ld c,a ; Store character in C ld a,(KeyFlags) rla jr c,DontClearFlags ; Lock - don't clear key flags xor a ld (KeyFlags),a CALL_(DrawStatus) DontClearFlags: ld hl,OutputLimit ld a,(OutputLength) ld d,0 ld e,a cp (hl) ret z ; No more chars allowed inc a ld (OutputLength),a ; Increase length of buffer ld hl,OutputBuffer add hl,de ; Calculate end of buffer ld (hl),C ; Add character to end of buffer Disp: ld hl,$3a40 ; X=64 Y=58 ld (CURSOR_X),hl ld a,(OutputLength) ld b,a ld hl,OutputBuffer ld a,(LineScroll) ld d,a ; Number of ScrollLeft's push af ld e,0 DispLoop: inc e ; Count how many ScrollLeft's we need push de CALL_(ScrollLeft) ; Scroll left half a screen CALL_(PrintLine) ; Print a line pop de jr c,DispOK ; No need to scroll (anymore) dec d jr nz,DispLoop ; MORE limits the ScrollLeft's DispOK: pop af dec a ; 0 is not a valid value...make it 255. bit 7,a jr z,DispNoSave ld a,e or $80 ld (LineScroll),a DispNoSave: ld a,(CURSOR_X) ld (CursorX),a ; Save the cursor X coordinate ret Clear: xor a ld (OutputLength),a ld (CursorX),a ld (KeyFlags),a ; Reset Num and Shift ld hl,VIDEO_MEM+928 CALL_(ClearLine) ; Clear input line CALL_(DrawStatus) ; Redraw status line ret Del: ld a,(OutputLength) sub 1 jr nc,DelOK xor a DelOK: ld (OutputLength),a ld hl,VIDEO_MEM+928 CALL_(ClearLine) jr Disp Alpha: ld a,(KeyFlags) cp $20 ; 0010 0000 - Shift jr z,KeySetLock cp $a0 ; 1010 0000 - ShiftLock jr z,KeyClear ld a,$20 ; 0010 0000 - Shift jr ChangeFlags Second: ld a,(KeyFlags) cp $40 ; 0100 0000 - Num jr z,KeySetLock cp $c0 ; 1100 0000 - NumLock jr z,KeyClear ld a,$40 ; 0100 0000 - Num jr ChangeFlags KeySetLock: or $80 ; 1000 0000 - Lock jr ChangeFlags KeyClear: xor a ChangeFlags: ld (KeyFlags),a CALL_(DrawStatus) ret F5: ld hl,ZS_BITS set 1,(hl) ; Exit ZShell immediately ld de,2 ; General call, msg 2 jr Exit2 Exit: ld de,1 ; General call, msg 1 Exit2: ld a,(InfoEntries) dec a jr z,Exit3 ; We're last - don't notify others ld a,$fe ; SendInfo protocol id CALL_(SendInfo) Exit3: ld hl,ZS_BITS set 0,(hl) ; Cause ZShell to recalculate checksum im 1 ; Disable interrupt routine pop af ; pop return address CALL_(ClearMem) ; Clear reserved memory ld hl,GRAPH_MEM ld bc,1023 ; Clear 1023 + 1 bytes JUMP_(ClearMemTail) ; Clear graph mem and exit ZShell ; Other subroutines ReadInBuffer: ld a,(I2CInputLength) or a jr nz,ReadInBufRead ; There's already data in buffer - read it! halt ; Delay, save power & run slave int routine ld a,(I2CInputLength) or a ret z ; Still no data received - return ReadInBufRead: ld a,(I2CInputBuffer+2) ; Look at protocol id byte cp $fe ; Is it a ReqInfo or SendInfo packet? jr nc,Info cp OurPid ; Is it our protocol id? jr nz,ClearInBuffer ; Ignore packets that aren't for us ; Shift backlog CALL_(LogShift) ; Load contents of input buffer into last place in log ld a,(I2CInputBuffer) ; Addr byte res 0,a ; Clear bit 0 (R/W bit) ld hl,InfoStart+10 ; Addr byte of 2nd Info db entry CALL_(InfoFind) jr nz,UnknownSender inc hl ; Id byte ld a,(hl) UnknownCont: ld hl,(FreeMemStart) ld de,LastEntry add hl,de ; HL = Start of last entry ld (hl),a ; Load id byte into last log entry inc hl ld a,(I2CInputBuffer+1) ; Data length byte dec a ; Remove protocol id ld (hl),a ; Set data length inc hl ld b,0 ld (hl),b ; Line count - unknown inc hl ld c,a ; Count : Data length ex de,hl ; Target: Last log entry, data ld hl,I2CInputBuffer+3 ; Source: Data from input log ldir CALL_(RedrawLog) jr ClearInBuffer UnknownSender: ld d,a ; Address to req. info from ld a,$ff ; ReqInfo protocol id ld e,0 ; msg 0 CALL_(SendInfo) ; Request Info update ld a,'?' ; Show this as sender id jr UnknownCont ClearInBuffer: xor a ld (I2CInputLength),a jr ReadInBuffer ; See if there's more data coming Info: rra ld a,$fe ; SendInfo protocol id ld de,0 CALL_C(SendInfo) ; Reply to ReqInfo packet ; Was it sent by another MChat program? ld de,I2CInputBuffer+3 ; Start of Info data ld hl,(PROGRAM_ADDR) ; Title string ld b,5 ; "MChat" has 6 chars InfoLoop: ld a,(de) cp (hl) jr nz,ClearInBuffer ; No match - we don't need the data inc hl inc de djnz InfoLoop ; A match - interpret the data ld a,(I2CInputBuffer+3+ProgStart) ; Msg byte from packet or a JUMP_NZ(InfoExit) ; Someone is exiting ; Someone is entering the chat or changing nick ld a,(I2CInputBuffer+4+ProgStart) ; Addr byte from packet ld hl,InfoStart+10 ; 2nd Info entry, addr byte (first is ours) CALL_(InfoFind) jr z,InfoChange ; New entry ; Compose notification message ld a,20 ; "xxxxxxxx has entered" - 20 bytes CALL_(InfoCompose) push de ld hl,(PROGRAM_ADDR) ld de,EnteringText add hl,de ld bc,12 ; 12 bytes for " has entered" pop de ldir ; Load entering text into entry CALL_(RedrawLog) ; Display entering message ld a,(InfoEntries) ld b,a ; How many entries must we skip? inc a cp 17 jr z,ClearInBuffer ; No room for more Info entries ld (InfoEntries),a ld hl,InfoStart ; Start of Info buffer ld de,10 ; Next entry is 10 bytes away InfoLoop2: add hl,de djnz InfoLoop2 InfoWrite: ex de,hl ; Target (DE): An Info entry ld hl,I2CInputBuffer+4+ProgStart ; Source: Info data in packet ld bc,10 ; Count: 10 bytes ldir ; Move the data jr ClearInBuffer ; Done - clear the buffer InfoChange: ; First check if there's a change - if not, don't display anything ld b,9 ; id char + 8 bytes for nickname = 9 bytes inc hl ; id byte of Info db entry push hl ; Save addr of db entry (id byte) ld de,I2CInputBuffer+5+ProgStart ; id byte of incoming packet InfoChangeLoop: ld a,(de) cp (hl) jr nz,InfoChangeOK ; There's a difference - change db and notify inc hl inc de djnz InfoChangeLoop pop hl JUMP_(ClearInBuffer) ; No changes - clear the buffer InfoChangeOK: ld a,35 ; "xxxxxxxx was known as yyyyyyyy id z" CALL_(InfoCompose) push de ld hl,(PROGRAM_ADDR) ld de,WasText add hl,de pop de ld bc,14 ; " was known as " ldir push hl pop bc ; BC=HL=Address of WasIdText pop hl ; HL = address of Info db entry (id byte) push hl inc hl ; Addr of previous nick push bc ld bc,8 ; nickname has 8 chars ldir ; Load previous nick into log entry pop hl ; HL = Address of WasIdText ld bc,4 ; " id " - 4 chars ldir pop hl ; HL = Address of id byte ldd push hl CALL_(RedrawLog) pop hl jr InfoWrite InfoExit: ld c,a ; InfoFind leaves C intact - save msg byte ld a,(I2CInputBuffer+4+ProgStart) ; Addr byte from packet ld hl,InfoStart+10 ; 2nd Info entry, addr byte (first is ours) CALL_(InfoFind) JUMP_NZ(ClearInBuffer) ; Unknown sender - just ignore ld a,c ; Msg byte push hl ; Save addr of Info entry (to be deleted) dec a jr nz,InfoTeacher ; Someone has pressed the Teacher Key! ld a,17 ; "xxxxxxxx has left" - 17 bytes CALL_(InfoCompose) push de ld hl,(PROGRAM_ADDR) ld de,LeavingText add hl,de ld bc,9 ; 9 bytes for " has left" InfoExitTail: ; used by InfoTeacher pop de ldir CALL_(RedrawLog) ; Now shift the Info database accordingly ld a,(InfoEntries) dec a ld (InfoEntries),a ; One less occupied entry pop hl ; Address of deleted entry push hl ; We need this address 3 times push hl push hl pop de ; DE=HL xor a ; Reset CY and clear A ld hl,InfoStart+150 sbc hl,de push hl pop bc ; Count: InfoStart+160-10-DE bytes pop hl ld de,10 add hl,de ; Source: Next entry after deleted pop de ; Target: Deleted entry cp c ; If count is zero, don't shift jr z,InfoExitClear ldir ; Shift remaining entries InfoExitClear: ld hl,0 ld (InfoStart+150),hl ; Clear addr and id bytes of last db entry JUMP_(ClearInBuffer) ; Done - clear buffer InfoTeacher: ld a,37 ; "xxxxxxxx has pressed the Teacher Key!" CALL_(InfoCompose) push de ld hl,(PROGRAM_ADDR) ld de,TeacherText add hl,de ld bc,29 ; " has pressed the Teacher Key!" - 29 chars jr InfoExitTail InfoCompose: CALL_(LogShift) ; DE = Start of last entry ld hl,I2CInputBuffer+5+ProgStart ; Id byte from packet ldi ; First byte of log entry: Id byte ld (de),a inc de xor a ld (de),a ; Line count: unknown (0) inc de ld bc,8 ldir ; Load nick into entry ret UnpackNumber: ld de,NumberScratch+4 ld b,5 ; 5 digits max UnpackLoop: call UNPACK_HL add a,'0' ld (de),a dec de djnz UnpackLoop ret DispHelp: ld a,c ld (CURSOR_X),a ROM_CALL(D_ZM_STR) ld a,(CURSOR_Y) add a,6 ld (CURSOR_Y),a djnz DispHelp ret PrintLine: ; Print a line of text xor a cp b scf ; CY=1: Whole string printed ret z ; Return if length is zero PrintLineLoop: ld a,(CURSOR_X) cp 120 ; Limit beyond which not to print ret nc ; CY=0: Only part of string printed ld a,(hl) ROM_CALL(M_CHARPUT) ; Put character in menu style font inc hl ; Next character djnz PrintLineLoop scf ; CY=1: Whole string printed ret ClearLine: ; Clear 6 rows from screen push af push bc ld b,6*16 xor a ClearLineLoop: ld (hl),a inc hl djnz ClearLineLoop pop bc pop af ret ScrollLeft: ; Scroll input line left (half a screen) push hl push bc ld de,VIDEO_MEM+928 ; Target: Upper left of input line ld hl,VIDEO_MEM+936 ; Source: Upper middle of input line ld b,6 ; Six screen rows ScrollLeftLoop: push bc ld bc,8 ; Count : 8 bytes = 64 pixels ldir push hl ld bc,8 ; 64 pixels ld hl,VIDEO_MEM+896 ; Source: Blank line over input line ldir pop hl push de ld de,8 add hl,de ; Add 8 to HL pop de pop bc djnz ScrollLeftLoop ld a,(CURSOR_X) ; Move the cursor left half a screen sub 64 ld (CURSOR_X),a pop bc pop hl ret DrawRow: ld b,16 ld a,$ff DrawRowLoop: ld (hl),a inc hl djnz DrawRowLoop ret SendInfo: push de ; D contains the address, E contains msg byte ld de,InfoBuffer ; Build Info packet in InfoBuffer ld hl,(PROGRAM_ADDR) ; Start with name of program ld bc,ProgStart ; Length is shown by the label ProgStart ldir ex de,hl pop de ld (hl),e ; Set msg byte to E inc hl push de ex de,hl ld hl,InfoStart ; Follow with first entry of the Info db ld bc,10 ; which is 10 bytes long ldir ld b,ProgStart+11 ; Length of Info packet pop de ; Restore address ld hl,InfoBuffer ; Data source ld c,3 ; Number of retries SendInfoRetry: CALL_(MI2C) ; Send the Info packet ret nc ; Packet sent OK push hl ld hl,(SendErrors) ; Increase Send Error counter inc hl ld (SendErrors),hl pop hl halt ; Delay - the receiver clears its buffer dec c jr nz,SendInfoRetry ret AnyKey: push hl xor a ld (LogScroll),a ; Disable chatlog display AnyKeyLoop: CALL_(ReadInBuffer) ; Handle incoming data & delay call GET_KEY or a jr z,AnyKeyLoop pop hl cp $31 ; "F5" JUMP_Z(F5) ; Teacher key exit ret ClearMem: ; Fill the Info database with spaces ld b,160 ; 160 bytes for Info data ld hl,InfoStart ; Start of Info db ld a,' ' ; Fill with spaces ClearMemLoop: ld (hl),a inc hl djnz ClearMemLoop ; Clear the allocated log memory ld hl,(FreeMemStart) ld bc,LogLength-1 ClearMemTail: ; Used to clear graph mem ld d,h ld e,l ld (hl),0 inc de ldir ret ; Exit back to ZShell - or to CALL_(ClearMem) DrawStatus: ld hl,VIDEO_MEM+816 CALL_(ClearLine) ld hl,$3200 ; X=0 Y=50 ld (CURSOR_X),hl ld hl,(PROGRAM_ADDR) ld de,StatusNickText add hl,de ROM_CALL(D_ZM_STR) ; Display "Nick: " ld hl,InfoStart+2 ; Own nickname ld b,8 ; Length: 8 chars ROM_CALL(D_LM_STR) ; Display the nickname ld a,75 ld (CURSOR_X),a ld hl,(PROGRAM_ADDR) ld de,StatusIdText add hl,de ROM_CALL(D_ZM_STR) ; Display "Id: " ld a,(InfoStart+1) ; Own id char ROM_CALL(M_CHARPUT) ; Print the id char ld a,(KeyFlags) and $7f ; Strip off lock bit ret z ; If no flags are set, we're done ld de,ShiftText cp $20 ; 0010 0000 - Shift jr z,DispFlags ld de,NumText DispFlags: ld a,96 ld (CURSOR_X),a ld hl,(PROGRAM_ADDR) add hl,de ROM_CALL(D_ZM_STR) ; Display flag ("Num" or "Shift") ld a,(KeyFlags) rla ret nc ; If no Lock flag is set, we're done ld hl,(PROGRAM_ADDR) ld de,LockText add hl,de ROM_CALL(D_ZM_STR) ; Display "Lock" ret LogShift: ld hl,(FreeMemStart) ; HL=Start of log push hl ld de,SecondEntry add hl,de ; Source: Start of second log entry pop de ; Target: Start of log ld bc,LastEntry ; Count ldir ; DE = Start of last entry ret RedrawLog: ld a,(LogScroll) or a ret z ; Don't print anything (wrong screen) RedrawLogStart: ld hl,(FreeMemStart) ld de,LastEntry+2 ; Line count byte of last entry add hl,de ; HL = line count byte of last entry push hl ; We need this later too ld a,(LogScroll) ; LogScroll shows where to start printing ld c,a ; (Measured as lines from end of buffer) ld b,16 ; 16 log entries ld de, -SecondEntry ; Distance between entries (going backwards) xor a ; Count lines in A RedrawLogL2: add a,(hl) cp c jr nc,RedrawLogPrint ; We have enough lines - now print them add hl,de ; Go to previous entry djnz RedrawLogL2 ; Not enough lines in the whole buffer! ld hl,(FreeMemStart) ld de,NinthEntry+2 add hl,de ; Start at ninth entry ld b,9 ; Print 17 - 9 = 8 entries ld a,(LogScroll) dec a cp 7 jr z,RedrawLogP ld (LogScroll),a pop hl ret ; Don't draw anything RedrawLogP: xor a RedrawLogPrint: push hl push bc ld hl,VIDEO_MEM ld b,8 ; Total: 8 lines RedrawLogL1: CALL_(ClearLine) djnz RedrawLogL1 pop bc pop hl dec hl dec hl ; HL=Start of first entry to print sub c ; C shows max amount of lines to print add a,8 ld d,a ; Show A + 8 - C lines ld a,17 sub b ld b,a ; Show 17 - B entries xor a ld (CURSOR_Y),a pop ix ; Line count byte of last entry ld a,(ix+0) or a jr nz,RedrawLogL3 ; No new, unmeasured lines at end of log ld d,0 ; New lines - print everything RedrawLogL3: push bc push hl ld a,(CURSOR_Y) cp 48 CALL_Z(Scroll) ; Scroll if needed xor a ld (CURSOR_X),a ld e,a ; Reset line counter for this entry ld a,(hl) ; id character or a jr z,RedrawLogNext ; id=0, next entry push de ROM_CALL(M_CHARPUT) ; Print id character (already in A) ld a,6 ld (CURSOR_X),a ld a,':' ROM_CALL(M_CHARPUT) pop de inc hl ld b,(hl) ; Chars for this entry inc hl ; Line count byte - not needed here inc hl ; Start of text string RedrawLogL4: ld a,8 ld (CURSOR_X),a ld a,(CURSOR_Y) cp 48 CALL_Z(Scroll) ; Scroll if needed push de CALL_(PrintLine) ; Print one line of entry pop de push af ; Save CY flag ld a,(CURSOR_Y) add a,6 ld (CURSOR_Y),a ; Advance to next line inc e ; One more line printed for this entry dec d ; Decrement total line counter jr z,RedrawLogDone ; Enough lines - stop pop af ; Restore CY jr nc,RedrawLogL4 ; One more line coming for this entry RedrawLogNext: ; Print next entry pop hl pop bc CALL_(RedrawLogTail) push de ld de,SecondEntry-2 ; Already advanced 2 bytes add hl,de ; Go to next entry pop de djnz RedrawLogL3 ; Print next entry ret ; Enough entries - stop RedrawLogDone: pop af pop hl pop bc RedrawLogTail: inc hl inc hl ; Line count byte of entry just printed ld a,(hl) cp e ret nc ; Don't change if E <= line count byte ld (hl),e ; Write new line count byte ret Scroll: ; Scroll log window up one line push bc push hl push de ld de,VIDEO_MEM ; Target: upper left of screen ld hl,VIDEO_MEM+96 ; Source: 6 rows below upper left of screen ld bc,7*6*16 ; Count : 7 lines * 6 rows ldir ld a,(CURSOR_Y) sub 6 ld (CURSOR_Y),a ld hl,VIDEO_MEM+672 CALL_(ClearLine) ; Clear last line pop de pop hl pop bc ret Cursor: ld a,(LineScroll) rla ret nc ; Scrolling with MORE - don't show cursor ld a,(CursorBlink) sub 1 ; This sets the CY flag - dec doesn't ld (CursorBlink),a ret nc ; No need to change cursor ld a,58 ld (CURSOR_Y),a ld a,(CursorX) ld (CURSOR_X),a set 3,(iy+05) ; XOR character with screen ld a,'_' ; The cursor block is an underscore ROM_CALL(M_CHARPUT) ; Put the cursor in place res 3,(iy+05) ; Print over character on screen ld a,50 ; Blink every 50 loops ld (CursorBlink),a ret InfoFind: ld b,15 ; Number of table entries (excluding first) ld de,10 ; next entry is 16 bytes away InfoFindLoop: cp (hl) ; Do the id bytes match? ret z ; A match was found - Z set add hl,de djnz InfoFindLoop ret ; No match - Z unset DrawMain: ROM_CALL(CLEARLCD) ld hl,VIDEO_MEM+784 CALL_(DrawRow) ; Draw line between chatlog and status line ld hl,VIDEO_MEM+912 CALL_(DrawRow) ; Draw line betw. status line and input line CALL_(DrawStatus) JUMP_(Disp) DispHex: ; Display (HL) in hex ld b,2 ; 2 hex digits xor a ; Clear four MSB's of A DispHexLoop: rld push af add a,'0' cp '9'+1 jr c,DispHexNumber add a,'A'-'0'-10 DispHexNumber: ROM_CALL(M_CHARPUT) pop af djnz DispHexLoop rld ret F2PrintCol: push bc push hl CALL_(DispHex) ; Display address (hexadecimal) ld a,c add a,9 ; 9 or 73 ld (CURSOR_X),a pop hl inc hl ld a,(hl) ROM_CALL(M_CHARPUT) ; Display id character ld a,c add a,16 ; 16 or 80 ld (CURSOR_X),a inc hl ld b,8 ; 8 characters ROM_CALL(D_LM_STR) ; Display nickname ld a,c ; 0 or 64 ld (CURSOR_X),a ld a,(CURSOR_Y) add a,6 ld (CURSOR_Y),a ; Scroll to next line pop bc djnz F2PrintCol ret #define NO_RAW #include "master.asm" #include "slave.asm" TitleText: .db "---------------------" .db " MChat v0.99 " .db "(C)1998 Osma Suominen" .db " " .db "---------------------",0 AddressText: .db "My bus address: ",0 MemErrorText: .db " Not enough memory! " .db " 2k of memory needed." .db " Press a key to exit",0 IdText: .db "Enter your id character:",0 DefId: ; Default id character (nothing) .db 0 DefNick: ; Default nickname length, default nickname .db 0," " NickText: .db "Enter your nickname " .db "(max 8 chars):",0 StatusNickText: .db "Nick: ",0 StatusIdText: .db "Id: ",0 ShiftText: .db "Shift",0 NumText: .db "Num",0 LockText: .db "Lock",0 F1Text: ; Left side: 10 lines .db "F1: Help",0 .db "F2: Nicknames",0 .db "F3: Stats",0 .db "F4: Change nick",0 .db "F5: Teacher key",0 .db 0 .db "2nd: Num (Lock)",0 .db "ALPHA: Shift (Lock)",0 .db "MORE: Scroll input line",0 .db "EXIT: Guess what?",0 ; Right side: 9 lines .db "GRAPH: :",0 .db "STAT: >",0 .db "Num-LOG: !",0 .db "Num-SIN: ?",0 .db "Num-COS: ",196,0 .db "Num-TAN: ",34,0 .db "Num-LN: ",206,0 .db 0 .db 30,31,": Scroll log",0 F2Text: .db "Ad Id Nickname",0 F3Text: .db "Statistics:",0 .db "Send Errors:",0 .db "Starts:",0 .db "Stops:",0 .db "Missed Starts:",0 .db "Timeouts:",0 .db "Checksum Errors:",0 .db "Speed: (hex)",0 .db 30,31," Change speed",0 PressText: .db "Press any key",0 ; Length indexed strings without trailing $0 EnteringText: .db " has entered" WasText: .db " was known as" ; Last space shared with next entry WasIdText: .db " id" ; Last space shared with next entry LeavingText: .db " has left" TeacherText: .db " has pressed the Teacher Key!" ; Translation tables Scan2Index: .db 1, 2, 3, 4, 5, 0, 0, 6, 7, 8, 9,10,11, 0, 0,12 .db 13,14,15,16,17, 0, 0,18,19,20,21,22,23,24,25, 0 .db 26,27,28,29,30,31 Index2Ascii: ; Plain .db 0,"xtoje wsnidzvrm" .db "hcyuqlgb>x=pkfa:" ; With Shift .db 0,"XTOJE WSNIDZVRM" .db "HCYUQLGB>x=PKFA:" ; With Num .db 0,"+-*/^-369)",34,".258" .db "(",196,"0147E?>x=,2",206,"!:" .end